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 ClassSpanNameAdditional Tags
InstanceRequestNacos/{$(lnstanceRequest.getType()}nacos.namespace
nacos.group
nacos.service.name
ServiceQueryRequestNacos/queryService
SubscribeServiceRequestNacos/subscribeService
Nacos/unsubscribeService
ServicelistRequestNacos/getServicelist
ConfigQueryRequestNacos/queryConfig
ConfigPublishRequestNacos/publishConfignacos.data.id
nacos.group
nacos.tenant
ConfigRemoveRequestNacos/removeConfig
ConfigChangeNotifyRequestNacos/notifyConfigChange
NotifySubscriberRequestNacos/notifySubscribeChangenacos.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")