diff --git a/.fossa.yml b/.fossa.yml
index fb2c373fe00e..5cc08dec8ddd 100644
--- a/.fossa.yml
+++ b/.fossa.yml
@@ -196,6 +196,9 @@ targets:
- type: gradle
path: ./
target: ':instrumentation:mybatis-3.2:javaagent'
+ - type: gradle
+ path: ./
+ target: ':instrumentation:nacos-client-2.0.0:javaagent'
- type: gradle
path: ./
target: ':instrumentation:opentelemetry-extension-annotations-1.0:javaagent'
diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md
index c09994f27d9e..b462172cc559 100644
--- a/docs/supported-libraries.md
+++ b/docs/supported-libraries.md
@@ -104,6 +104,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 |
+| [Nacos Client](https://nacos.io/) | 2.0.0+ | N/A | none |
| [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/nacos-client-2.0.0/README.md b/instrumentation/nacos-client-2.0.0/README.md
new file mode 100644
index 000000000000..00bbd73dcd17
--- /dev/null
+++ b/instrumentation/nacos-client-2.0.0/README.md
@@ -0,0 +1,61 @@
+## Enhancement Methods
+- `com.alibaba.nacos.common.remote.client.grpc.GrpcConnection#request`
+- `com.alibaba.nacos.common.remote.client.RpcClient#handleServerRequest`
+
+## Enable Configuration
+
+| Configuration Items | Default Value |
+|:---------------------------------------------|:---------------|
+| `otel.instrumentation.nacos-client.enabled` | `false` |
+
+## Span Info Details
+
+
+
+ Request Child Class |
+ SpanName |
+ Additional Tags |
+
+
+
+
+ InstanceRequest |
+ Nacos/{$(lnstanceRequest.getType()} |
+ nacos.namespace nacos.group nacos.service.name |
+
+
+ ServiceQueryRequest |
+ Nacos/queryService |
+
+
+ SubscribeServiceRequest |
+ Nacos/subscribeService Nacos/unsubscribeService |
+
+
+ ServicelistRequest |
+ Nacos/getServicelist |
+
+
+ ConfigQueryRequest |
+ Nacos/queryConfig |
+
+
+ ConfigPublishRequest |
+ Nacos/publishConfig |
+ nacos.data.id nacos.group nacos.tenant |
+
+
+ ConfigRemoveRequest |
+ Nacos/removeConfig |
+
+
+ ConfigChangeNotifyRequest |
+ Nacos/notifyConfigChange |
+
+
+ NotifySubscriberRequest |
+ Nacos/notifySubscribeChange |
+ nacos.group nacos.service.name |
+
+
+
diff --git a/instrumentation/nacos-client-2.0.0/javaagent/build.gradle.kts b/instrumentation/nacos-client-2.0.0/javaagent/build.gradle.kts
new file mode 100644
index 000000000000..7ea00a3aef7c
--- /dev/null
+++ b/instrumentation/nacos-client-2.0.0/javaagent/build.gradle.kts
@@ -0,0 +1,24 @@
+plugins {
+ id("otel.javaagent-instrumentation")
+}
+
+muzzle {
+ pass {
+ group.set("com.alibaba.nacos")
+ module.set("nacos-client")
+ versions.set("[2.0.0,)")
+ skip("0.5.0", "0.6.1", "1.1.2", "1.1.4", "1.4.7")
+ assertInverse.set(true)
+ }
+}
+
+dependencies {
+ library("com.alibaba.nacos:nacos-client:2.0.0")
+ testImplementation("javax.annotation:javax.annotation-api:1.3.2")
+
+ latestDepTestLibrary("com.alibaba.nacos:nacos-client:2.0.4+") // documented limitation
+}
+
+tasks.withType().configureEach {
+ jvmArgs("-Dotel.instrumentation.nacos-client.enabled=true")
+}
diff --git a/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientConstants.java b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientConstants.java
new file mode 100644
index 000000000000..16332aac4e93
--- /dev/null
+++ b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientConstants.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0;
+
+import io.opentelemetry.api.common.AttributeKey;
+
+public class NacosClientConstants {
+ private NacosClientConstants() {}
+
+ public static final String NACOS_PREFIX = "Nacos/";
+
+ public static final String SERVER_CHECK = "serverCheck";
+
+ public static final String QUERY_SERVICE = "queryService";
+
+ public static final String SUBSCRIBE_SERVICE = "subscribeService";
+
+ public static final String UNSUBSCRIBE_SERVICE = "unsubscribeService";
+
+ public static final String QUERY_CONFIG = "queryConfig";
+
+ public static final String PUBLISH_CONFIG = "publishConfig";
+
+ public static final String REMOVE_CONFIG = "removeConfig";
+
+ public static final String GET_SERVICE_LIST = "getServiceList";
+
+ public static final String NOTIFY_SUBSCRIBE_CHANGE = "notifySubscribeChange";
+
+ public static final String NOTIFY_CONFIG_CHANGE = "notifyConfigChange";
+
+ public static final AttributeKey NACOS_NAME_SPACE_ATTR =
+ AttributeKey.stringKey("nacos.namespace");
+
+ public static final AttributeKey NACOS_GROUP_NAME_ATTR =
+ AttributeKey.stringKey("nacos.group.name");
+
+ public static final AttributeKey NACOS_SERVICE_NAME_ATTR =
+ AttributeKey.stringKey("nacos.service.name");
+
+ public static final AttributeKey NACOS_DATA_ID_ATTR =
+ AttributeKey.stringKey("nacos.data.id");
+
+ public static final AttributeKey NACOS_GROUP_ATTR = AttributeKey.stringKey("nacos.group");
+
+ public static final AttributeKey NACOS_TENANT_ATTR =
+ AttributeKey.stringKey("nacos.tenant");
+}
diff --git a/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientHelper.java b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientHelper.java
new file mode 100644
index 000000000000..d9cad42288c5
--- /dev/null
+++ b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientHelper.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0;
+
+import com.alibaba.nacos.api.config.remote.request.ConfigChangeNotifyRequest;
+import com.alibaba.nacos.api.config.remote.request.ConfigPublishRequest;
+import com.alibaba.nacos.api.config.remote.request.ConfigQueryRequest;
+import com.alibaba.nacos.api.config.remote.request.ConfigRemoveRequest;
+import com.alibaba.nacos.api.naming.remote.request.InstanceRequest;
+import com.alibaba.nacos.api.naming.remote.request.NotifySubscriberRequest;
+import com.alibaba.nacos.api.naming.remote.request.ServiceListRequest;
+import com.alibaba.nacos.api.naming.remote.request.ServiceQueryRequest;
+import com.alibaba.nacos.api.naming.remote.request.SubscribeServiceRequest;
+import com.alibaba.nacos.api.remote.request.Request;
+import java.util.HashMap;
+import java.util.Map;
+import javax.annotation.Nonnull;
+
+public class NacosClientHelper {
+ private static final NacosClientRequestOperator UNKNOWN_OPERATOR =
+ new NacosClientRequestOperator(request -> request.getClass().getSimpleName(), null);
+ private static final Map, NacosClientRequestOperator>
+ KNOWN_OPERATOR_MAP = new HashMap<>();
+
+ private NacosClientHelper() {}
+
+ static {
+ KNOWN_OPERATOR_MAP.put(
+ InstanceRequest.class,
+ new NacosClientRequestOperator(
+ request -> ((InstanceRequest) request).getType(),
+ (attributesBuilder, request) -> {
+ InstanceRequest instanceRequest = (InstanceRequest) request;
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_NAME_SPACE_ATTR, instanceRequest.getNamespace());
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_GROUP_NAME_ATTR, instanceRequest.getGroupName());
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_SERVICE_NAME_ATTR, instanceRequest.getServiceName());
+ }));
+
+ KNOWN_OPERATOR_MAP.put(
+ ServiceQueryRequest.class,
+ new NacosClientRequestOperator(
+ request -> NacosClientConstants.QUERY_SERVICE,
+ (attributesBuilder, request) -> {
+ ServiceQueryRequest serviceQueryRequest = (ServiceQueryRequest) request;
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_NAME_SPACE_ATTR, serviceQueryRequest.getNamespace());
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_GROUP_NAME_ATTR, serviceQueryRequest.getGroupName());
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_SERVICE_NAME_ATTR,
+ serviceQueryRequest.getServiceName());
+ }));
+
+ KNOWN_OPERATOR_MAP.put(
+ SubscribeServiceRequest.class,
+ new NacosClientRequestOperator(
+ request ->
+ ((SubscribeServiceRequest) request).isSubscribe()
+ ? NacosClientConstants.SUBSCRIBE_SERVICE
+ : NacosClientConstants.UNSUBSCRIBE_SERVICE,
+ (attributesBuilder, request) -> {
+ SubscribeServiceRequest subscribeServiceRequest = (SubscribeServiceRequest) request;
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_NAME_SPACE_ATTR,
+ subscribeServiceRequest.getNamespace());
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_GROUP_NAME_ATTR,
+ subscribeServiceRequest.getGroupName());
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_SERVICE_NAME_ATTR,
+ subscribeServiceRequest.getServiceName());
+ }));
+
+ KNOWN_OPERATOR_MAP.put(
+ ServiceListRequest.class,
+ new NacosClientRequestOperator(
+ request -> NacosClientConstants.GET_SERVICE_LIST,
+ (attributesBuilder, request) -> {
+ ServiceListRequest serviceListRequest = (ServiceListRequest) request;
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_NAME_SPACE_ATTR, serviceListRequest.getNamespace());
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_GROUP_NAME_ATTR, serviceListRequest.getGroupName());
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_SERVICE_NAME_ATTR,
+ serviceListRequest.getServiceName());
+ }));
+
+ KNOWN_OPERATOR_MAP.put(
+ ConfigQueryRequest.class,
+ new NacosClientRequestOperator(
+ request -> NacosClientConstants.QUERY_CONFIG,
+ (attributesBuilder, request) -> {
+ ConfigQueryRequest configQueryRequest = (ConfigQueryRequest) request;
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_DATA_ID_ATTR, configQueryRequest.getDataId());
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_GROUP_ATTR, configQueryRequest.getGroup());
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_TENANT_ATTR, configQueryRequest.getTenant());
+ }));
+
+ KNOWN_OPERATOR_MAP.put(
+ ConfigPublishRequest.class,
+ new NacosClientRequestOperator(
+ request -> NacosClientConstants.PUBLISH_CONFIG,
+ (attributesBuilder, request) -> {
+ ConfigPublishRequest configPublishRequest = (ConfigPublishRequest) request;
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_DATA_ID_ATTR, configPublishRequest.getDataId());
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_GROUP_ATTR, configPublishRequest.getGroup());
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_TENANT_ATTR, configPublishRequest.getTenant());
+ }));
+
+ KNOWN_OPERATOR_MAP.put(
+ ConfigRemoveRequest.class,
+ new NacosClientRequestOperator(
+ request -> NacosClientConstants.REMOVE_CONFIG,
+ (attributesBuilder, request) -> {
+ ConfigRemoveRequest configRemoveRequest = (ConfigRemoveRequest) request;
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_DATA_ID_ATTR, configRemoveRequest.getDataId());
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_GROUP_ATTR, configRemoveRequest.getGroup());
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_TENANT_ATTR, configRemoveRequest.getTenant());
+ }));
+
+ KNOWN_OPERATOR_MAP.put(
+ NotifySubscriberRequest.class,
+ new NacosClientRequestOperator(
+ request -> NacosClientConstants.NOTIFY_SUBSCRIBE_CHANGE,
+ (attributesBuilder, request) -> {
+ NotifySubscriberRequest notifySubscriberRequest = (NotifySubscriberRequest) request;
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_NAME_SPACE_ATTR,
+ notifySubscriberRequest.getNamespace());
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_GROUP_NAME_ATTR,
+ notifySubscriberRequest.getGroupName());
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_SERVICE_NAME_ATTR,
+ notifySubscriberRequest.getServiceName());
+ }));
+
+ KNOWN_OPERATOR_MAP.put(
+ ConfigChangeNotifyRequest.class,
+ new NacosClientRequestOperator(
+ request -> NacosClientConstants.NOTIFY_CONFIG_CHANGE,
+ (attributesBuilder, request) -> {
+ ConfigChangeNotifyRequest configChangeNotifyRequest =
+ (ConfigChangeNotifyRequest) request;
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_DATA_ID_ATTR, configChangeNotifyRequest.getDataId());
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_GROUP_ATTR, configChangeNotifyRequest.getGroup());
+ attributesBuilder.put(
+ NacosClientConstants.NACOS_TENANT_ATTR, configChangeNotifyRequest.getTenant());
+ }));
+ }
+
+ @Nonnull
+ public static NacosClientRequestOperator getOperator(@Nonnull Request request) {
+ NacosClientRequestOperator nacosClientRequestOperator =
+ KNOWN_OPERATOR_MAP.get(request.getClass());
+ if (nacosClientRequestOperator != null) {
+ return nacosClientRequestOperator;
+ }
+ return UNKNOWN_OPERATOR;
+ }
+}
diff --git a/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientInstrumentationModule.java b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientInstrumentationModule.java
new file mode 100644
index 000000000000..fca4f3a4b8d4
--- /dev/null
+++ b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientInstrumentationModule.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0;
+
+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 io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.instrumentations.GrpcConnectionInstrumentation;
+import io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.instrumentations.RpcClientInstrumentation;
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
+import java.util.List;
+
+@AutoService(InstrumentationModule.class)
+public class NacosClientInstrumentationModule extends InstrumentationModule {
+ public NacosClientInstrumentationModule() {
+ super("nacos-client", "nacos-client-2.0.0");
+ }
+
+ @Override
+ public List typeInstrumentations() {
+ return asList(new GrpcConnectionInstrumentation(), new RpcClientInstrumentation());
+ }
+
+ @Override
+ public boolean defaultEnabled(ConfigProperties config) {
+ return false;
+ }
+}
diff --git a/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientRequest.java b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientRequest.java
new file mode 100644
index 000000000000..313b3f487c51
--- /dev/null
+++ b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientRequest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0;
+
+import com.alibaba.nacos.api.remote.request.Request;
+import io.opentelemetry.api.common.Attributes;
+import javax.annotation.Nonnull;
+
+public final class NacosClientRequest {
+ private final String methodName;
+ private final Class> declaringClass;
+ private final String spanName;
+ private final Attributes attributes;
+
+ public NacosClientRequest(
+ String methodName, Class> declaringClass, String spanName, Attributes attributes) {
+ this.methodName = methodName;
+ this.declaringClass = declaringClass;
+ this.spanName = spanName;
+ this.attributes = attributes;
+ }
+
+ @Nonnull
+ public static NacosClientRequest createRequest(
+ @Nonnull String methodName, @Nonnull Class> declaringClass, @Nonnull Request request) {
+ NacosClientRequestOperator operator = NacosClientHelper.getOperator(request);
+ String spanName = operator.getName(request);
+ Attributes attributes = operator.getAttributes(request);
+ return new NacosClientRequest(methodName, declaringClass, spanName, attributes);
+ }
+
+ public String getMethodName() {
+ return methodName;
+ }
+
+ public Class> getDeclaringClass() {
+ return declaringClass;
+ }
+
+ public String getSpanName() {
+ return spanName;
+ }
+
+ public Attributes getAttributes() {
+ return attributes;
+ }
+}
diff --git a/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientRequestOperator.java b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientRequestOperator.java
new file mode 100644
index 000000000000..600b058f6058
--- /dev/null
+++ b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientRequestOperator.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0;
+
+import com.alibaba.nacos.api.remote.request.Request;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.common.AttributesBuilder;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+public class NacosClientRequestOperator {
+ private final Function spanNameHandler;
+ private final BiConsumer attributesHandler;
+
+ public NacosClientRequestOperator(
+ Function spanNameHandler,
+ BiConsumer attributesHandler) {
+ this.spanNameHandler = spanNameHandler;
+ this.attributesHandler = attributesHandler;
+ }
+
+ public String getName(Request request) {
+ return spanNameHandler == null
+ ? ""
+ : NacosClientConstants.NACOS_PREFIX + spanNameHandler.apply(request);
+ }
+
+ public Attributes getAttributes(Request request) {
+ AttributesBuilder builder = Attributes.builder();
+ if (attributesHandler != null) {
+ attributesHandler.accept(builder, request);
+ }
+ return builder.build();
+ }
+}
diff --git a/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientSingletons.java b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientSingletons.java
new file mode 100644
index 000000000000..984d927d7e67
--- /dev/null
+++ b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientSingletons.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0;
+
+import com.alibaba.nacos.api.remote.response.Response;
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesExtractor;
+import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesGetter;
+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.SpanNameExtractor;
+import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor;
+import io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.extractors.NacosClientCodeAttributesGetter;
+import io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.extractors.NacosClientExperimentalAttributeExtractor;
+import io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.extractors.NacosClientSpanNameExtractor;
+import io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.extractors.NacosClientSpanStatusExtractor;
+
+public final class NacosClientSingletons {
+ private static final String INSTRUMENTATION_NAME = "io.opentelemetry.nacos-client-2.0.0";
+ private static final Instrumenter INSTRUMENTER = create();
+
+ public static Instrumenter instrumenter() {
+ return INSTRUMENTER;
+ }
+
+ private static Instrumenter create() {
+ CodeAttributesGetter codeAttributesGetter =
+ new NacosClientCodeAttributesGetter();
+ SpanNameExtractor spanNameExtractor = new NacosClientSpanNameExtractor();
+ SpanStatusExtractor spanStatusExtractor =
+ new NacosClientSpanStatusExtractor();
+ InstrumenterBuilder builder =
+ Instrumenter.builder(
+ GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, spanNameExtractor)
+ .addAttributesExtractor(CodeAttributesExtractor.create(codeAttributesGetter))
+ .setSpanStatusExtractor(spanStatusExtractor);
+ builder.addAttributesExtractor(
+ AttributesExtractor.constant(AttributeKey.stringKey("service.discovery.system"), "nacos"));
+ builder.addAttributesExtractor(new NacosClientExperimentalAttributeExtractor());
+ return builder.buildInstrumenter();
+ }
+
+ private NacosClientSingletons() {}
+}
diff --git a/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/extractors/NacosClientCodeAttributesGetter.java b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/extractors/NacosClientCodeAttributesGetter.java
new file mode 100644
index 000000000000..897514e20262
--- /dev/null
+++ b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/extractors/NacosClientCodeAttributesGetter.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.extractors;
+
+import io.opentelemetry.instrumentation.api.incubator.semconv.code.CodeAttributesGetter;
+import io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.NacosClientRequest;
+import javax.annotation.Nullable;
+
+public class NacosClientCodeAttributesGetter implements CodeAttributesGetter {
+ @Nullable
+ @Override
+ public Class> getCodeClass(NacosClientRequest nacosClientRequest) {
+ return nacosClientRequest.getDeclaringClass();
+ }
+
+ @Nullable
+ @Override
+ public String getMethodName(NacosClientRequest nacosClientRequest) {
+ return nacosClientRequest.getMethodName();
+ }
+}
diff --git a/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/extractors/NacosClientExperimentalAttributeExtractor.java b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/extractors/NacosClientExperimentalAttributeExtractor.java
new file mode 100644
index 000000000000..c476bdb573be
--- /dev/null
+++ b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/extractors/NacosClientExperimentalAttributeExtractor.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.extractors;
+
+import com.alibaba.nacos.api.remote.response.Response;
+import io.opentelemetry.api.common.AttributesBuilder;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
+import io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.NacosClientRequest;
+import javax.annotation.Nullable;
+
+public class NacosClientExperimentalAttributeExtractor
+ implements AttributesExtractor {
+ @Override
+ public void onStart(
+ AttributesBuilder attributes, Context parentContext, NacosClientRequest nacosClientRequest) {
+ attributes.putAll(nacosClientRequest.getAttributes());
+ }
+
+ @Override
+ public void onEnd(
+ AttributesBuilder attributes,
+ Context context,
+ NacosClientRequest nacosClientRequest,
+ @Nullable Response response,
+ @Nullable Throwable error) {}
+}
diff --git a/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/extractors/NacosClientSpanNameExtractor.java b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/extractors/NacosClientSpanNameExtractor.java
new file mode 100644
index 000000000000..61c43cf3abd9
--- /dev/null
+++ b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/extractors/NacosClientSpanNameExtractor.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.extractors;
+
+import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
+import io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.NacosClientRequest;
+
+public class NacosClientSpanNameExtractor implements SpanNameExtractor {
+ @Override
+ public String extract(NacosClientRequest nacosClientRequest) {
+ return nacosClientRequest.getSpanName();
+ }
+}
diff --git a/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/extractors/NacosClientSpanStatusExtractor.java b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/extractors/NacosClientSpanStatusExtractor.java
new file mode 100644
index 000000000000..0b8158f9784a
--- /dev/null
+++ b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/extractors/NacosClientSpanStatusExtractor.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.extractors;
+
+import com.alibaba.nacos.api.remote.response.Response;
+import io.opentelemetry.api.trace.StatusCode;
+import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusBuilder;
+import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor;
+import io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.NacosClientRequest;
+import javax.annotation.Nullable;
+
+public class NacosClientSpanStatusExtractor
+ implements SpanStatusExtractor {
+ @Override
+ public void extract(
+ SpanStatusBuilder spanStatusBuilder,
+ NacosClientRequest nacosClientRequest,
+ @Nullable Response response,
+ @Nullable Throwable error) {
+ if (response == null || !response.isSuccess()) {
+ spanStatusBuilder.setStatus(StatusCode.ERROR);
+ } else {
+ SpanStatusExtractor.getDefault()
+ .extract(spanStatusBuilder, nacosClientRequest, response, error);
+ }
+ }
+}
diff --git a/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/instrumentations/GrpcConnectionInstrumentation.java b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/instrumentations/GrpcConnectionInstrumentation.java
new file mode 100644
index 000000000000..f86e4a5e4d95
--- /dev/null
+++ b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/instrumentations/GrpcConnectionInstrumentation.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.instrumentations;
+
+import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
+import static io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.NacosClientSingletons.instrumenter;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.isPublic;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.returns;
+
+import com.alibaba.nacos.api.remote.request.Request;
+import com.alibaba.nacos.api.remote.response.Response;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.NacosClientRequest;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+public class GrpcConnectionInstrumentation implements TypeInstrumentation {
+ @Override
+ public ElementMatcher typeMatcher() {
+ return named("com.alibaba.nacos.common.remote.client.grpc.GrpcConnection");
+ }
+
+ @Override
+ public void transform(TypeTransformer transformer) {
+ transformer.applyAdviceToMethod(
+ isMethod()
+ .and(isPublic())
+ .and(named("request"))
+ .and(returns(named("com.alibaba.nacos.api.remote.response.Response"))),
+ GrpcConnectionInstrumentation.class.getName() + "$GrpcConnectionRequestAdvice");
+ }
+
+ public static class GrpcConnectionRequestAdvice {
+ @SuppressWarnings("unused")
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static void requestEnter(
+ @Advice.This Object thisObject,
+ @Advice.Argument(0) Request request,
+ @Advice.Local("otelRequest") NacosClientRequest nacosClientRequest,
+ @Advice.Local("otelContext") Context context,
+ @Advice.Local("otelScope") Scope scope) {
+ Context parentContext = currentContext();
+ nacosClientRequest =
+ NacosClientRequest.createRequest("request", thisObject.getClass(), request);
+ if (!instrumenter().shouldStart(parentContext, nacosClientRequest)) {
+ return;
+ }
+ context = instrumenter().start(parentContext, nacosClientRequest);
+ scope = context.makeCurrent();
+ }
+
+ @SuppressWarnings("unused")
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
+ public static void requestExit(
+ @Advice.Return Response response,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Local("otelRequest") NacosClientRequest nacosClientRequest,
+ @Advice.Local("otelContext") Context context,
+ @Advice.Local("otelScope") Scope scope) {
+ if (scope == null) {
+ return;
+ }
+ scope.close();
+ instrumenter().end(context, nacosClientRequest, response, throwable);
+ }
+ }
+}
diff --git a/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/instrumentations/RpcClientInstrumentation.java b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/instrumentations/RpcClientInstrumentation.java
new file mode 100644
index 000000000000..06e9df1c0afa
--- /dev/null
+++ b/instrumentation/nacos-client-2.0.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/instrumentations/RpcClientInstrumentation.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.instrumentations;
+
+import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
+import static io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.NacosClientSingletons.instrumenter;
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
+import static net.bytebuddy.matcher.ElementMatchers.isProtected;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.returns;
+
+import com.alibaba.nacos.api.remote.request.Request;
+import com.alibaba.nacos.api.remote.response.Response;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
+import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
+import io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.NacosClientRequest;
+import net.bytebuddy.asm.Advice;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.matcher.ElementMatcher;
+
+public class RpcClientInstrumentation implements TypeInstrumentation {
+ @Override
+ public ElementMatcher typeMatcher() {
+ return named("com.alibaba.nacos.common.remote.client.RpcClient");
+ }
+
+ @Override
+ public void transform(TypeTransformer transformer) {
+ transformer.applyAdviceToMethod(
+ isMethod()
+ .and(isProtected())
+ .and(named("handleServerRequest"))
+ .and(returns(named("com.alibaba.nacos.api.remote.response.Response"))),
+ RpcClientInstrumentation.class.getName() + "$RpcClientHandleServerRequestAdvice");
+ }
+
+ public static class RpcClientHandleServerRequestAdvice {
+ @SuppressWarnings("unused")
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static void handleServerRequestEnter(
+ @Advice.This Object thisObject,
+ @Advice.Argument(0) Request request,
+ @Advice.Local("otelRequest") NacosClientRequest nacosClientRequest,
+ @Advice.Local("otelContext") Context context,
+ @Advice.Local("otelScope") Scope scope) {
+ Context parentContext = currentContext();
+ nacosClientRequest =
+ NacosClientRequest.createRequest("handleServerRequest", thisObject.getClass(), request);
+ if (!instrumenter().shouldStart(parentContext, nacosClientRequest)) {
+ return;
+ }
+ context = instrumenter().start(parentContext, nacosClientRequest);
+ scope = context.makeCurrent();
+ }
+
+ @SuppressWarnings("unused")
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
+ public static void handleServerRequestExit(
+ @Advice.Return Response response,
+ @Advice.Thrown Throwable throwable,
+ @Advice.Local("otelRequest") NacosClientRequest nacosClientRequest,
+ @Advice.Local("otelContext") Context context,
+ @Advice.Local("otelScope") Scope scope) {
+ if (scope == null) {
+ return;
+ }
+ scope.close();
+ instrumenter().end(context, nacosClientRequest, response, throwable);
+ }
+ }
+}
diff --git a/instrumentation/nacos-client-2.0.0/javaagent/src/test/java/com/alibaba/nacos/common/remote/client/RpcClientTest.java b/instrumentation/nacos-client-2.0.0/javaagent/src/test/java/com/alibaba/nacos/common/remote/client/RpcClientTest.java
new file mode 100644
index 000000000000..9b3425a133c4
--- /dev/null
+++ b/instrumentation/nacos-client-2.0.0/javaagent/src/test/java/com/alibaba/nacos/common/remote/client/RpcClientTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.alibaba.nacos.common.remote.client;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import com.alibaba.nacos.api.remote.request.Request;
+import com.alibaba.nacos.api.remote.response.Response;
+import com.alibaba.nacos.common.remote.ConnectionType;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
+import io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.NacosClientTestHelper;
+import io.opentelemetry.sdk.trace.data.StatusData;
+import java.util.Collections;
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+public class RpcClientTest {
+ @RegisterExtension
+ private static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
+
+ private RpcClient rpcClient;
+
+ @Mock private ServerRequestHandler serverRequestHandler;
+
+ @BeforeEach
+ public void setUp() {
+ rpcClient =
+ new RpcClient("testRpcClient") {
+ @Override
+ public ConnectionType getConnectionType() {
+ return ConnectionType.GRPC;
+ }
+
+ @Override
+ public int rpcPortOffset() {
+ return 0;
+ }
+
+ @Override
+ public Connection connectToServer(ServerInfo serverInfo) throws Exception {
+ return null;
+ }
+ };
+ rpcClient.serverRequestHandlers = Collections.singletonList(serverRequestHandler);
+ }
+
+ @ParameterizedTest
+ @MethodSource("requestProvider")
+ public void handleServerRequestSuccessResponse(Request request) {
+ when(serverRequestHandler.requestReply(any(Request.class)))
+ .thenReturn(NacosClientTestHelper.SUCCESS_RESPONSE);
+ Response response = rpcClient.handleServerRequest(request);
+ assertNotNull(response);
+ assertTrue(response.isSuccess());
+ testing.waitAndAssertTraces(
+ trace -> {
+ trace.hasSpansSatisfyingExactly(
+ span -> {
+ span.hasName(NacosClientTestHelper.NACOS_CLIENT_REQUEST_NAME_MAP.get(request))
+ .hasKind(SpanKind.INTERNAL)
+ .hasStatus(StatusData.unset())
+ .hasAttributesSatisfyingExactly(
+ NacosClientTestHelper.requestAttributeAssertions(
+ rpcClient.getClass().getName(), "handleServerRequest", request));
+ });
+ });
+ testing.clearData();
+ }
+
+ @ParameterizedTest
+ @MethodSource("requestProvider")
+ public void handleServerRequestErrorResponse(Request request) {
+ when(serverRequestHandler.requestReply(any(Request.class)))
+ .thenReturn(NacosClientTestHelper.ERROR_RESPONSE);
+ Response response = rpcClient.handleServerRequest(request);
+ assertNotNull(response);
+ assertFalse(response.isSuccess());
+ testing.waitAndAssertTraces(
+ trace -> {
+ trace.hasSpansSatisfyingExactly(
+ span -> {
+ span.hasName(NacosClientTestHelper.NACOS_CLIENT_REQUEST_NAME_MAP.get(request))
+ .hasKind(SpanKind.INTERNAL)
+ .hasStatus(StatusData.error())
+ .hasAttributesSatisfyingExactly(
+ NacosClientTestHelper.requestAttributeAssertions(
+ rpcClient.getClass().getName(), "handleServerRequest", request));
+ });
+ });
+ testing.clearData();
+ }
+
+ private static List requestProvider() {
+ return NacosClientTestHelper.REQUEST_LIST;
+ }
+}
diff --git a/instrumentation/nacos-client-2.0.0/javaagent/src/test/java/com/alibaba/nacos/common/remote/client/grpc/GrpcConnectionTest.java b/instrumentation/nacos-client-2.0.0/javaagent/src/test/java/com/alibaba/nacos/common/remote/client/grpc/GrpcConnectionTest.java
new file mode 100644
index 000000000000..3e82fc99e0d8
--- /dev/null
+++ b/instrumentation/nacos-client-2.0.0/javaagent/src/test/java/com/alibaba/nacos/common/remote/client/grpc/GrpcConnectionTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.alibaba.nacos.common.remote.client.grpc;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import com.alibaba.nacos.api.exception.NacosException;
+import com.alibaba.nacos.api.grpc.auto.Payload;
+import com.alibaba.nacos.api.grpc.auto.RequestGrpc;
+import com.alibaba.nacos.api.remote.PayloadRegistry;
+import com.alibaba.nacos.api.remote.request.Request;
+import com.alibaba.nacos.api.remote.response.Response;
+import com.alibaba.nacos.common.remote.client.RpcClient;
+import com.alibaba.nacos.shaded.com.google.common.util.concurrent.ListenableFuture;
+import com.alibaba.nacos.shaded.io.grpc.ManagedChannel;
+import com.alibaba.nacos.shaded.io.grpc.stub.StreamObserver;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
+import io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0.NacosClientTestHelper;
+import io.opentelemetry.sdk.trace.data.StatusData;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+public class GrpcConnectionTest {
+
+ @RegisterExtension
+ private static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
+
+ @Mock private Executor executor;
+
+ @Mock private ManagedChannel channel;
+
+ @Mock private StreamObserver payloadStreamObserver;
+
+ @Mock RequestGrpc.RequestFutureStub grpcFutureServiceStub;
+
+ @Mock ListenableFuture future;
+
+ Payload responsePayload;
+
+ Payload errorResponsePayload;
+
+ GrpcConnection connection;
+
+ @BeforeAll
+ public static void setUpBeforeClass() {
+ PayloadRegistry.init();
+ }
+
+ @BeforeEach
+ public void setUp() throws Exception {
+ connection = new GrpcConnection(new RpcClient.ServerInfo(), executor);
+ connection.setChannel(channel);
+ connection.setPayloadStreamObserver(payloadStreamObserver);
+ connection.setGrpcFutureServiceStub(grpcFutureServiceStub);
+ when(grpcFutureServiceStub.request(any(Payload.class))).thenReturn(future);
+ responsePayload = GrpcUtils.convert(NacosClientTestHelper.SUCCESS_RESPONSE);
+ errorResponsePayload = GrpcUtils.convert(NacosClientTestHelper.ERROR_RESPONSE);
+ }
+
+ @AfterEach
+ public void tearDown() {
+ connection.close();
+ }
+
+ @ParameterizedTest
+ @MethodSource("requestProvider")
+ public void requestSuccessResponse(Request request)
+ throws NacosException, ExecutionException, InterruptedException, TimeoutException {
+ when(future.get(-1, TimeUnit.MILLISECONDS)).thenReturn(responsePayload);
+ Response response = connection.request(request, -1);
+ assertNotNull(response);
+ assertTrue(response.isSuccess());
+ testing.waitAndAssertTraces(
+ trace -> {
+ trace.hasSpansSatisfyingExactly(
+ span -> {
+ span.hasName(NacosClientTestHelper.NACOS_CLIENT_REQUEST_NAME_MAP.get(request))
+ .hasKind(SpanKind.INTERNAL)
+ .hasStatus(StatusData.unset())
+ .hasAttributesSatisfyingExactly(
+ NacosClientTestHelper.requestAttributeAssertions(
+ connection.getClass().getName(), "request", request));
+ });
+ });
+ testing.clearData();
+ }
+
+ @ParameterizedTest
+ @MethodSource("requestProvider")
+ public void requestErrorResponse(Request request)
+ throws NacosException, ExecutionException, InterruptedException, TimeoutException {
+ when(future.get(-1, TimeUnit.MILLISECONDS)).thenReturn(errorResponsePayload);
+ Response response = connection.request(request, -1);
+ assertNotNull(response);
+ assertFalse(response.isSuccess());
+ testing.waitAndAssertTraces(
+ trace -> {
+ trace.hasSpansSatisfyingExactly(
+ span -> {
+ span.hasName(NacosClientTestHelper.NACOS_CLIENT_REQUEST_NAME_MAP.get(request))
+ .hasKind(SpanKind.INTERNAL)
+ .hasStatus(StatusData.error())
+ .hasAttributesSatisfyingExactly(
+ NacosClientTestHelper.requestAttributeAssertions(
+ connection.getClass().getName(), "request", request));
+ });
+ });
+ testing.clearData();
+ }
+
+ private static List requestProvider() {
+ return NacosClientTestHelper.REQUEST_LIST;
+ }
+}
diff --git a/instrumentation/nacos-client-2.0.0/javaagent/src/test/java/com/alibaba/nacos/shaded/com/google/errorprone/annotations/DoNotMock.java b/instrumentation/nacos-client-2.0.0/javaagent/src/test/java/com/alibaba/nacos/shaded/com/google/errorprone/annotations/DoNotMock.java
new file mode 100644
index 000000000000..6ffdaf0a6a19
--- /dev/null
+++ b/instrumentation/nacos-client-2.0.0/javaagent/src/test/java/com/alibaba/nacos/shaded/com/google/errorprone/annotations/DoNotMock.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.alibaba.nacos.shaded.com.google.errorprone.annotations;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Target(TYPE)
+@Retention(RUNTIME)
+@Documented
+public @interface DoNotMock {
+ String value();
+}
diff --git a/instrumentation/nacos-client-2.0.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientTestHelper.java b/instrumentation/nacos-client-2.0.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientTestHelper.java
new file mode 100644
index 000000000000..46a5ad6aaacf
--- /dev/null
+++ b/instrumentation/nacos-client-2.0.0/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/nacos/client/v2_0_0/NacosClientTestHelper.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.nacos.client.v2_0_0;
+
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
+import static java.util.Arrays.asList;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.alibaba.nacos.api.config.remote.request.ConfigChangeNotifyRequest;
+import com.alibaba.nacos.api.config.remote.request.ConfigPublishRequest;
+import com.alibaba.nacos.api.config.remote.request.ConfigQueryRequest;
+import com.alibaba.nacos.api.config.remote.request.ConfigRemoveRequest;
+import com.alibaba.nacos.api.naming.remote.request.InstanceRequest;
+import com.alibaba.nacos.api.naming.remote.request.NotifySubscriberRequest;
+import com.alibaba.nacos.api.naming.remote.request.ServiceListRequest;
+import com.alibaba.nacos.api.naming.remote.request.ServiceQueryRequest;
+import com.alibaba.nacos.api.naming.remote.request.SubscribeServiceRequest;
+import com.alibaba.nacos.api.remote.request.Request;
+import com.alibaba.nacos.api.remote.response.HealthCheckResponse;
+import com.alibaba.nacos.api.remote.response.Response;
+import com.alibaba.nacos.api.remote.response.ResponseCode;
+import io.opentelemetry.api.common.AttributeKey;
+import io.opentelemetry.sdk.testing.assertj.AttributeAssertion;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class NacosClientTestHelper {
+ public static final List RPC_CLIENT_HANDLE_SERVER_REQUEST_REQUEST_LIST =
+ new ArrayList<>();
+ public static final List GRPC_CONNECTION_REQUEST_LIST = new ArrayList<>();
+ public static final List REQUEST_LIST = new ArrayList<>();
+ public static final Map NACOS_CLIENT_REQUEST_MAP = new HashMap<>();
+ public static final Map NACOS_CLIENT_REQUEST_NAME_MAP = new HashMap<>();
+
+ public static final String NAMESPACE = "namespace";
+ public static final String GROUP_NAME = "groupName";
+ public static final String SERVICE_NAME = "serviceName";
+ public static final String DATA_ID = "dataId";
+ public static final String GROUP = "group";
+ public static final String TENANT = "tenant";
+ public static final String INSTANCE_REQUEST_TYPE = "instanceRequestType";
+ public static final ConfigChangeNotifyRequest CONFIG_CHANGE_NOTIFY_REQUEST;
+ public static final ConfigPublishRequest CONFIG_PUBLISH_REQUEST;
+ public static final ConfigQueryRequest CONFIG_QUERY_REQUEST;
+ public static final ConfigRemoveRequest CONFIG_REMOVE_REQUEST;
+ public static final InstanceRequest INSTANCE_REQUEST;
+ public static final NotifySubscriberRequest NOTIFY_SUBSCRIBER_REQUEST;
+ public static final ServiceListRequest SERVICE_LIST_REQUEST;
+ public static final ServiceQueryRequest SERVICE_QUERY_REQUEST;
+ public static final SubscribeServiceRequest SUBSCRIBE_SERVICE_REQUEST;
+ public static final SubscribeServiceRequest UN_SUBSCRIBE_SERVICE_REQUEST;
+
+ public static final Response SUCCESS_RESPONSE;
+ public static final Response ERROR_RESPONSE;
+ public static final Response NULL_RESPONSE;
+
+ public static final String NACOS_PREFIX = "Nacos/";
+
+ public static final String QUERY_SERVICE = "queryService";
+
+ public static final String SUBSCRIBE_SERVICE = "subscribeService";
+
+ public static final String UNSUBSCRIBE_SERVICE = "unsubscribeService";
+
+ public static final String QUERY_CONFIG = "queryConfig";
+
+ public static final String PUBLISH_CONFIG = "publishConfig";
+
+ public static final String REMOVE_CONFIG = "removeConfig";
+
+ public static final String GET_SERVICE_LIST = "getServiceList";
+
+ public static final String NOTIFY_SUBSCRIBE_CHANGE = "notifySubscribeChange";
+
+ public static final String NOTIFY_CONFIG_CHANGE = "notifyConfigChange";
+
+ public static final AttributeKey NACOS_NAME_SPACE_ATTR =
+ AttributeKey.stringKey("nacos.namespace");
+
+ public static final AttributeKey NACOS_GROUP_NAME_ATTR =
+ AttributeKey.stringKey("nacos.group.name");
+
+ public static final AttributeKey NACOS_SERVICE_NAME_ATTR =
+ AttributeKey.stringKey("nacos.service.name");
+
+ public static final AttributeKey NACOS_DATA_ID_ATTR =
+ AttributeKey.stringKey("nacos.data.id");
+
+ public static final AttributeKey NACOS_GROUP_ATTR = AttributeKey.stringKey("nacos.group");
+
+ public static final AttributeKey NACOS_TENANT_ATTR =
+ AttributeKey.stringKey("nacos.tenant");
+
+ private NacosClientTestHelper() {}
+
+ static {
+ SUCCESS_RESPONSE = new HealthCheckResponse();
+ HealthCheckResponse errorResponse = new HealthCheckResponse();
+ errorResponse.setResultCode(ResponseCode.FAIL.getCode());
+ ERROR_RESPONSE = errorResponse;
+ NULL_RESPONSE = null;
+
+ INSTANCE_REQUEST = mock(InstanceRequest.class);
+ when(INSTANCE_REQUEST.getType()).thenReturn(INSTANCE_REQUEST_TYPE);
+ when(INSTANCE_REQUEST.getNamespace()).thenReturn(NAMESPACE);
+ when(INSTANCE_REQUEST.getGroupName()).thenReturn(GROUP_NAME);
+ when(INSTANCE_REQUEST.getServiceName()).thenReturn(SERVICE_NAME);
+
+ SERVICE_QUERY_REQUEST = mock(ServiceQueryRequest.class);
+ when(SERVICE_QUERY_REQUEST.getNamespace()).thenReturn(NAMESPACE);
+ when(SERVICE_QUERY_REQUEST.getGroupName()).thenReturn(GROUP_NAME);
+ when(SERVICE_QUERY_REQUEST.getServiceName()).thenReturn(SERVICE_NAME);
+
+ SUBSCRIBE_SERVICE_REQUEST = mock(SubscribeServiceRequest.class);
+ when(SUBSCRIBE_SERVICE_REQUEST.isSubscribe()).thenReturn(true);
+ when(SUBSCRIBE_SERVICE_REQUEST.getNamespace()).thenReturn(NAMESPACE);
+ when(SUBSCRIBE_SERVICE_REQUEST.getGroupName()).thenReturn(GROUP_NAME);
+ when(SUBSCRIBE_SERVICE_REQUEST.getServiceName()).thenReturn(SERVICE_NAME);
+
+ UN_SUBSCRIBE_SERVICE_REQUEST = mock(SubscribeServiceRequest.class);
+ when(UN_SUBSCRIBE_SERVICE_REQUEST.isSubscribe()).thenReturn(false);
+ when(UN_SUBSCRIBE_SERVICE_REQUEST.getNamespace()).thenReturn(NAMESPACE);
+ when(UN_SUBSCRIBE_SERVICE_REQUEST.getGroupName()).thenReturn(GROUP_NAME);
+ when(UN_SUBSCRIBE_SERVICE_REQUEST.getServiceName()).thenReturn(SERVICE_NAME);
+
+ SERVICE_LIST_REQUEST = mock(ServiceListRequest.class);
+ when(SERVICE_LIST_REQUEST.getNamespace()).thenReturn(NAMESPACE);
+ when(SERVICE_LIST_REQUEST.getGroupName()).thenReturn(GROUP_NAME);
+ when(SERVICE_LIST_REQUEST.getServiceName()).thenReturn(SERVICE_NAME);
+
+ CONFIG_QUERY_REQUEST = mock(ConfigQueryRequest.class);
+ when(CONFIG_QUERY_REQUEST.getDataId()).thenReturn(DATA_ID);
+ when(CONFIG_QUERY_REQUEST.getGroup()).thenReturn(GROUP);
+ when(CONFIG_QUERY_REQUEST.getTenant()).thenReturn(TENANT);
+
+ CONFIG_PUBLISH_REQUEST = mock(ConfigPublishRequest.class);
+ when(CONFIG_PUBLISH_REQUEST.getDataId()).thenReturn(DATA_ID);
+ when(CONFIG_PUBLISH_REQUEST.getGroup()).thenReturn(GROUP);
+ when(CONFIG_PUBLISH_REQUEST.getTenant()).thenReturn(TENANT);
+
+ CONFIG_REMOVE_REQUEST = mock(ConfigRemoveRequest.class);
+ when(CONFIG_REMOVE_REQUEST.getDataId()).thenReturn(DATA_ID);
+ when(CONFIG_REMOVE_REQUEST.getGroup()).thenReturn(GROUP);
+ when(CONFIG_REMOVE_REQUEST.getTenant()).thenReturn(TENANT);
+
+ NOTIFY_SUBSCRIBER_REQUEST = mock(NotifySubscriberRequest.class);
+ when(NOTIFY_SUBSCRIBER_REQUEST.getNamespace()).thenReturn(NAMESPACE);
+ when(NOTIFY_SUBSCRIBER_REQUEST.getGroupName()).thenReturn(GROUP_NAME);
+ when(NOTIFY_SUBSCRIBER_REQUEST.getServiceName()).thenReturn(SERVICE_NAME);
+
+ CONFIG_CHANGE_NOTIFY_REQUEST = mock(ConfigChangeNotifyRequest.class);
+ when(CONFIG_CHANGE_NOTIFY_REQUEST.getDataId()).thenReturn(DATA_ID);
+ when(CONFIG_CHANGE_NOTIFY_REQUEST.getGroup()).thenReturn(GROUP);
+ when(CONFIG_CHANGE_NOTIFY_REQUEST.getTenant()).thenReturn(TENANT);
+
+ NACOS_CLIENT_REQUEST_MAP.put(NACOS_PREFIX + NOTIFY_CONFIG_CHANGE, CONFIG_CHANGE_NOTIFY_REQUEST);
+ NACOS_CLIENT_REQUEST_MAP.put(NACOS_PREFIX + PUBLISH_CONFIG, CONFIG_PUBLISH_REQUEST);
+ NACOS_CLIENT_REQUEST_MAP.put(NACOS_PREFIX + QUERY_CONFIG, CONFIG_QUERY_REQUEST);
+ NACOS_CLIENT_REQUEST_MAP.put(NACOS_PREFIX + REMOVE_CONFIG, CONFIG_REMOVE_REQUEST);
+ NACOS_CLIENT_REQUEST_MAP.put(NACOS_PREFIX + INSTANCE_REQUEST_TYPE, INSTANCE_REQUEST);
+ NACOS_CLIENT_REQUEST_MAP.put(NACOS_PREFIX + NOTIFY_SUBSCRIBE_CHANGE, NOTIFY_SUBSCRIBER_REQUEST);
+ NACOS_CLIENT_REQUEST_MAP.put(NACOS_PREFIX + GET_SERVICE_LIST, SERVICE_LIST_REQUEST);
+ NACOS_CLIENT_REQUEST_MAP.put(NACOS_PREFIX + QUERY_SERVICE, SERVICE_QUERY_REQUEST);
+ NACOS_CLIENT_REQUEST_MAP.put(NACOS_PREFIX + SUBSCRIBE_SERVICE, SUBSCRIBE_SERVICE_REQUEST);
+ NACOS_CLIENT_REQUEST_MAP.put(NACOS_PREFIX + UNSUBSCRIBE_SERVICE, UN_SUBSCRIBE_SERVICE_REQUEST);
+
+ for (Request value : NACOS_CLIENT_REQUEST_MAP.values()) {
+ REQUEST_LIST.add(value);
+ if (value instanceof NotifySubscriberRequest || value instanceof ConfigChangeNotifyRequest) {
+ RPC_CLIENT_HANDLE_SERVER_REQUEST_REQUEST_LIST.add(value);
+ } else {
+ GRPC_CONNECTION_REQUEST_LIST.add(value);
+ }
+ }
+ NACOS_CLIENT_REQUEST_NAME_MAP.putAll(
+ NACOS_CLIENT_REQUEST_MAP.entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)));
+ }
+
+ private static final String[] methodNames = {
+ "getNamespace", "getGroupName", "getServiceName", "getDataId", "getGroup", "getTenant"
+ };
+
+ private static final Map> methodToAttributeMap = new HashMap<>();
+
+ static {
+ methodToAttributeMap.put("getNamespace", NACOS_NAME_SPACE_ATTR);
+ methodToAttributeMap.put("getGroupName", NACOS_GROUP_NAME_ATTR);
+ methodToAttributeMap.put("getServiceName", NACOS_SERVICE_NAME_ATTR);
+ methodToAttributeMap.put("getDataId", NACOS_DATA_ID_ATTR);
+ methodToAttributeMap.put("getGroup", NACOS_GROUP_ATTR);
+ methodToAttributeMap.put("getTenant", NACOS_TENANT_ATTR);
+ }
+
+ public static List requestAttributeAssertions(
+ String codeNamespace, String codeFunction, Request request) {
+ List attributeAssertions =
+ new ArrayList<>(
+ asList(
+ equalTo(AttributeKey.stringKey("code.namespace"), codeNamespace),
+ equalTo(AttributeKey.stringKey("code.function"), codeFunction),
+ equalTo(AttributeKey.stringKey("service.discovery.system"), "nacos")));
+
+ for (String methodName : methodNames) {
+ Method method = null;
+ try {
+ method = request.getClass().getMethod(methodName);
+ } catch (NoSuchMethodException e) {
+ continue;
+ }
+ if (method != null) {
+ method.setAccessible(true);
+ Object result = null;
+ try {
+ result = method.invoke(request);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ attributeAssertions.add(
+ equalTo(methodToAttributeMap.get(methodName), String.valueOf(result)));
+ }
+ }
+
+ return attributeAssertions;
+ }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 22fb50e8f1f9..a8bf825c5da8 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -406,6 +406,7 @@ 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:nacos-client-2.0.0:javaagent")
include(":instrumentation:netty:netty-3.8:javaagent")
include(":instrumentation:netty:netty-4.0:javaagent")
include(":instrumentation:netty:netty-4.1:javaagent")