Add the Nacos-Client 2.x plugin#18758
Conversation
|
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 18 changed files in this pull request and generated 15 comments.
Comments suppressed due to low confidence (2)
instrumentation/nacos-client-2.0/metadata.yaml:3
- [Documentation] This module builds RPC client/consumer spans, but the metadata does not declare any
semantic_conventionsentries. Existing RPC modules such asinstrumentation/grpc-1.6/metadata.yaml:3-7andinstrumentation/apache-dubbo-2.7/metadata.yaml:5-9declare their RPC span/metric signals so generated docs and the instrumentation registry classify the emitted telemetry correctly.
description: This instrumentation provides a template for Nacos client 2.x spans.
disabled_by_default: true
instrumentation/nacos-client-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/nacosclient/v2_0/NacosClientInstrumentationModule.java:33
- [General] The module is registered but explicitly disabled by default, while the PR/linked issue describe adding Nacos 2.x instrumentation to cover requests that shaded gRPC cannot capture. As written, users will still get no Nacos spans after upgrading unless they discover and set
otel.instrumentation.nacos-client.enabled=true; either make the instrumentation enabled by default or document the opt-in rationale clearly.
@Override
public boolean defaultEnabled() {
return false;
}
| @@ -0,0 +1,4 @@ | |||
| display_name: Nacos Client | |||
| description: This instrumentation provides a template for Nacos client 2.x spans. | |||
| return asList( | ||
| new GrpcClientInstrumentation(), | ||
| new GrpcConnectionInstrumentation(), | ||
| new RpcClientInstrumentation()); |
| .addAttributesExtractor(new NacosClientAttributesExtractor()); | ||
| return builder.buildInstrumenter(SpanKindExtractor.alwaysConsumer()); |
| import com.alibaba.nacos.api.remote.request.Request; | ||
| import javax.annotation.Nullable; | ||
|
|
||
| public final class NacosClientRequest { |
| import io.opentelemetry.context.Context; | ||
| import io.opentelemetry.context.Scope; | ||
|
|
||
| public final class ContextAndScope { |
| final class NacosClientRpcAttributesGetter | ||
| implements RpcAttributesGetter<NacosClientRequest, Response> { |
| final class NacosClientNetworkAttributesGetter | ||
| implements ServerAttributesGetter<NacosClientRequest>, | ||
| NetworkAttributesGetter<NacosClientRequest, Response> { |
| final class NacosClientAttributesExtractor | ||
| implements AttributesExtractor<NacosClientRequest, Response> { |
| request, category, operation, peer, hostPort.host(), hostPort.port()); | ||
| } | ||
|
|
||
| private static final class HostPort { |
| } | ||
| } | ||
|
|
||
| private static final class ServerCheckRequest extends Request { |
| @Override | ||
| public void transform(TypeTransformer transformer) { | ||
| transformer.applyAdviceToMethod( | ||
| isMethod() |
| if (rawRequest instanceof InstanceRequest) { | ||
| InstanceRequest instanceRequest = (InstanceRequest) rawRequest; | ||
| put(attributes, NACOS_NAMESPACE, instanceRequest.getNamespace()); | ||
| put(attributes, NACOS_GROUP, instanceRequest.getGroupName()); | ||
| put(attributes, NACOS_SERVICE_NAME, instanceRequest.getServiceName()); | ||
| } else if (rawRequest instanceof ServiceQueryRequest) { | ||
| ServiceQueryRequest serviceQueryRequest = (ServiceQueryRequest) rawRequest; | ||
| put(attributes, NACOS_NAMESPACE, serviceQueryRequest.getNamespace()); | ||
| put(attributes, NACOS_GROUP, serviceQueryRequest.getGroupName()); | ||
| put(attributes, NACOS_SERVICE_NAME, serviceQueryRequest.getServiceName()); | ||
| } else if (rawRequest instanceof SubscribeServiceRequest) { | ||
| SubscribeServiceRequest subscribeServiceRequest = (SubscribeServiceRequest) rawRequest; | ||
| put(attributes, NACOS_NAMESPACE, subscribeServiceRequest.getNamespace()); | ||
| put(attributes, NACOS_GROUP, subscribeServiceRequest.getGroupName()); | ||
| put(attributes, NACOS_SERVICE_NAME, subscribeServiceRequest.getServiceName()); | ||
| } else if (rawRequest instanceof ServiceListRequest) { | ||
| ServiceListRequest serviceListRequest = (ServiceListRequest) rawRequest; | ||
| put(attributes, NACOS_NAMESPACE, serviceListRequest.getNamespace()); | ||
| put(attributes, NACOS_GROUP, serviceListRequest.getGroupName()); | ||
| put(attributes, NACOS_SERVICE_NAME, serviceListRequest.getServiceName()); |
There was a problem hiding this comment.
all of these are subclasses of AbstractNamingRequest could consider using instanceof AbstractNamingRequest
| NacosClientRequest request, @Nullable Response response, @Nullable Throwable error) { | ||
| if (response != null && !response.isSuccess()) { | ||
| int errorCode = response.getErrorCode(); | ||
| return errorCode == 0 ? "response_error" : String.valueOf(errorCode); |
| return builder.buildInstrumenter(SpanKindExtractor.alwaysClient()); | ||
| } | ||
|
|
||
| private static Instrumenter<NacosClientRequest, Response> createServerInstrumenter() { |
There was a problem hiding this comment.
consider removing Client from classes shared by client and server instrumentation
| if (response != null && !response.isSuccess()) { | ||
| String message = response.getMessage(); | ||
| int errorCode = response.getErrorCode(); | ||
| return new IllegalStateException( |
There was a problem hiding this comment.
Creating an exception here doesn't make sense since the stack trace is not going to be useful for the end user. Consider implementing a SpanStatusExtractor extractor instead that sets span status to error when the response wasn't successful.
| if (serverInfo == null) { | ||
| serverInfo = rpcClient.getCurrentServer(); | ||
| } | ||
| return serverInfo != null ? serverInfo.getAddress() : "unknown"; |
There was a problem hiding this comment.
our convention is to leave attributes unfilled if a value can't be extracted
| testImplementation(project(":instrumentation-api")) | ||
| testImplementation(project(":instrumentation-api-incubator")) | ||
|
|
||
| testImplementation("io.opentelemetry:opentelemetry-api") | ||
| testImplementation("io.opentelemetry:opentelemetry-context") | ||
| testImplementation("com.alibaba.nacos:nacos-client:2.0.0") |
There was a problem hiding this comment.
I think these aren't needed
| systemProperty("collectMetadata", otelProps.collectMetadata) | ||
| } | ||
|
|
||
| val testAgentInstrumentation by tasks.registering(Test::class) { |
There was a problem hiding this comment.
I think you don't need a separate test task for this, could just add jvmArgs("-Dotel.instrumentation.nacos-client.enabled=true") to the main test task.
| "co.elastic.clients:elasticsearch-java#+": "9.4.1", | ||
| "co.elastic.clients:elasticsearch-java#8.9.+": "8.9.2", | ||
| "com.alibaba:druid#+": "1.2.28", | ||
| "com.alibaba.nacos:nacos-client#+": "2.5.2", |
There was a problem hiding this comment.
did you manually edit this? should the latest version be 3.2.1?
| @Advice.Argument(0) String serverIp, | ||
| @Advice.Argument(1) int serverPort) { | ||
| RpcClientServerInfoAccessor.set(rpcClient, new RpcClient.ServerInfo(serverIp, serverPort)); | ||
| return NacosClientSingletons.startClientSpan( |
There was a problem hiding this comment.
This somewhat deviates from wheat we usually do. Many instrumentations use a class named AdviceScope to pass context, scope and request to end advice. Using some other class is also fine, for example here instead of ContextAndScope you could pass a class that contains context, scope and request and knows how to end the span.
#18290
nacos-clientutilizes the Maven Shade plugin to prevent class conflicts. Consequently, gRPC instrumentation is unable to capture the requests.