Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .fossa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,9 @@ targets:
- type: gradle
path: ./
target: ':instrumentation:mybatis-3.2:javaagent'
- type: gradle
path: ./
target: ':instrumentation:nacos-client-2.0:javaagent'
- type: gradle
path: ./
target: ':instrumentation:opentelemetry-extension-annotations-1.0:javaagent'
Expand Down
2 changes: 2 additions & 0 deletions .github/config/latest-dep-versions.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"co.elastic.clients:elasticsearch-java#+": "9.4.2",
"co.elastic.clients:elasticsearch-java#8.9.+": "8.9.2",
"com.alibaba:druid#+": "1.2.28",
"com.alibaba.nacos:nacos-client#+": "2.5.2",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you manually edit this? should the latest version be 3.2.1?

"com.alibaba.nacos:nacos-client#2.+": "2.5.2",
"com.amazonaws:aws-java-sdk-core#+": "1.12.797",
"com.amazonaws:aws-java-sdk-dynamodb#+": "1.12.797",
"com.amazonaws:aws-java-sdk-ec2#+": "1.12.797",
Expand Down
52 changes: 52 additions & 0 deletions instrumentation/nacos-client-2.0/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("com.alibaba.nacos")
module.set("nacos-client")
versions.set("[2.0.0,3.0.0)")
assertInverse.set(true)
}
}

dependencies {
library("com.alibaba.nacos:nacos-client:2.0.0")
latestDepTestLibrary("com.alibaba.nacos:nacos-client:2.+") // documented limitation

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")
Comment on lines +18 to +23

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these aren't needed

}

tasks.withType<Test>().configureEach {
systemProperty("collectMetadata", otelProps.collectMetadata)
}

val testAgentInstrumentation by tasks.registering(Test::class) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

testClassesDirs = sourceSets.test.get().output.classesDirs
classpath = sourceSets.test.get().runtimeClasspath
include("**/NacosClientAgentInstrumentationTest.class")

jvmArgs("-Dotel.instrumentation.nacos-client.enabled=true")
}

tasks {
test {
exclude("**/NacosClientAgentInstrumentationTest.class")
}

check {
dependsOn(testAgentInstrumentation)
}
}

afterEvaluate {
tasks.withType<Test>().configureEach {
classpath += sourceSets.main.get().output
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.nacosclient.v2_0;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;

public class ContextAndScope {
private final Context context;
private final Scope scope;

public ContextAndScope(Context context, Scope scope) {
this.context = context;
this.scope = scope;
}

public Context getContext() {
return context;
}

public void closeScope() {
scope.close();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.nacosclient.v2_0;

import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import com.alibaba.nacos.api.remote.response.Response;
import com.alibaba.nacos.common.remote.client.RpcClient;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import javax.annotation.Nullable;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

class GrpcClientInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("com.alibaba.nacos.common.remote.client.grpc.GrpcClient");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isMethod() isn't needed

.and(named("serverCheck"))
.and(takesArguments(3))
.and(takesArgument(0, String.class))
.and(takesArgument(1, int.class))
.and(
takesArgument(
2, named("com.alibaba.nacos.api.grpc.auto.RequestGrpc$RequestFutureStub"))),
getClass().getName() + "$ServerCheckAdvice");
}

@SuppressWarnings("unused")
public static class ServerCheckAdvice {
@Nullable
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static ContextAndScope onEnter(
@Advice.This RpcClient rpcClient,
@Advice.Argument(0) String serverIp,
@Advice.Argument(1) int serverPort) {
RpcClientServerInfoAccessor.set(rpcClient, new RpcClient.ServerInfo(serverIp, serverPort));
return NacosClientSingletons.startClientSpan(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

NacosRequestMapper.createServerCheckRequest(serverIp, serverPort));
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class, inline = false)
public static void onExit(
@Advice.Argument(0) String serverIp,
@Advice.Argument(1) int serverPort,
@Advice.Enter @Nullable ContextAndScope contextAndScope,
@Advice.Return @Nullable Response response,
@Advice.Thrown @Nullable Throwable throwable) {
NacosClientSingletons.endClientSpan(
contextAndScope,
NacosRequestMapper.createServerCheckRequest(serverIp, serverPort),
response,
throwable);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.nacosclient.v2_0;

import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;

import com.alibaba.nacos.api.remote.request.Request;
import com.alibaba.nacos.api.remote.response.Response;
import com.alibaba.nacos.common.remote.client.grpc.GrpcConnection;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import javax.annotation.Nullable;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

class GrpcConnectionInstrumentation implements TypeInstrumentation {

@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("com.alibaba.nacos.common.remote.client.grpc.GrpcConnection");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
isMethod()
.and(named("request"))
.and(takesArguments(2))
.and(takesArgument(0, named("com.alibaba.nacos.api.remote.request.Request")))
.and(takesArgument(1, long.class)),
getClass().getName() + "$RequestAdvice");
}

@SuppressWarnings("unused")
public static class RequestAdvice {

@Nullable
@Advice.OnMethodEnter(suppress = Throwable.class, inline = false)
public static State onEnter(
@Advice.This GrpcConnection connection, @Advice.Argument(0) Request request) {
NacosClientRequest nacosRequest =
NacosRequestMapper.mapClientRequest(request, connection.getChannel().authority());
if (nacosRequest == null) {
return null;
}
ContextAndScope contextAndScope = NacosClientSingletons.startClientSpan(nacosRequest);
return contextAndScope == null ? null : new State(nacosRequest, contextAndScope);
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class, inline = false)
public static void onExit(
@Advice.Enter @Nullable State state,
@Advice.Return @Nullable Response response,
@Advice.Thrown @Nullable Throwable throwable) {
if (state == null) {
return;
}
NacosClientSingletons.endClientSpan(
state.contextAndScope(), state.request(), response, throwable);
}
}

public static class State {
private final NacosClientRequest request;
private final ContextAndScope contextAndScope;

public State(NacosClientRequest request, ContextAndScope contextAndScope) {
this.request = request;
this.contextAndScope = contextAndScope;
}

public NacosClientRequest request() {
return request;
}

public ContextAndScope contextAndScope() {
return contextAndScope;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.nacosclient.v2_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.pojo.ServiceInfo;
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.response.Response;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.Context;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import javax.annotation.Nullable;

class NacosClientAttributesExtractor implements AttributesExtractor<NacosClientRequest, Response> {

private static final AttributeKey<String> NACOS_CATEGORY =
AttributeKey.stringKey("nacos.category");
private static final AttributeKey<String> NACOS_REQUEST_TYPE =
AttributeKey.stringKey("nacos.request.type");
private static final AttributeKey<String> NACOS_NAMESPACE =
AttributeKey.stringKey("nacos.namespace");
private static final AttributeKey<String> NACOS_GROUP = AttributeKey.stringKey("nacos.group");
private static final AttributeKey<String> NACOS_SERVICE_NAME =
AttributeKey.stringKey("nacos.service.name");
private static final AttributeKey<String> NACOS_DATA_ID = AttributeKey.stringKey("nacos.data.id");
private static final AttributeKey<String> NACOS_TENANT = AttributeKey.stringKey("nacos.tenant");

@Override
public void onStart(
AttributesBuilder attributes, Context parentContext, NacosClientRequest request) {
attributes.put(NACOS_CATEGORY, request.category());
attributes.put(NACOS_REQUEST_TYPE, request.request().getClass().getSimpleName());

Object rawRequest = request.request();
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());
Comment on lines +46 to +65

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all of these are subclasses of AbstractNamingRequest could consider using instanceof AbstractNamingRequest

} else if (rawRequest instanceof ConfigQueryRequest) {
ConfigQueryRequest configQueryRequest = (ConfigQueryRequest) rawRequest;
put(attributes, NACOS_DATA_ID, configQueryRequest.getDataId());
put(attributes, NACOS_GROUP, configQueryRequest.getGroup());
put(attributes, NACOS_TENANT, configQueryRequest.getTenant());
} else if (rawRequest instanceof ConfigPublishRequest) {
ConfigPublishRequest configPublishRequest = (ConfigPublishRequest) rawRequest;
put(attributes, NACOS_DATA_ID, configPublishRequest.getDataId());
put(attributes, NACOS_GROUP, configPublishRequest.getGroup());
put(attributes, NACOS_TENANT, configPublishRequest.getTenant());
} else if (rawRequest instanceof ConfigRemoveRequest) {
ConfigRemoveRequest configRemoveRequest = (ConfigRemoveRequest) rawRequest;
put(attributes, NACOS_DATA_ID, configRemoveRequest.getDataId());
put(attributes, NACOS_GROUP, configRemoveRequest.getGroup());
put(attributes, NACOS_TENANT, configRemoveRequest.getTenant());
} else if (rawRequest instanceof NotifySubscriberRequest) {
ServiceInfo serviceInfo = ((NotifySubscriberRequest) rawRequest).getServiceInfo();
if (serviceInfo != null) {
put(attributes, NACOS_GROUP, serviceInfo.getGroupName());
put(attributes, NACOS_SERVICE_NAME, serviceInfo.getName());
}
} else if (rawRequest instanceof ConfigChangeNotifyRequest) {
ConfigChangeNotifyRequest configChangeNotifyRequest = (ConfigChangeNotifyRequest) rawRequest;
put(attributes, NACOS_DATA_ID, configChangeNotifyRequest.getDataId());
put(attributes, NACOS_GROUP, configChangeNotifyRequest.getGroup());
put(attributes, NACOS_TENANT, configChangeNotifyRequest.getTenant());
}
}

@Override
public void onEnd(
AttributesBuilder attributes,
Context context,
NacosClientRequest request,
@Nullable Response response,
@Nullable Throwable error) {}

private static void put(
AttributesBuilder attributes, AttributeKey<String> key, @Nullable String value) {
if (value != null && !value.isEmpty()) {
attributes.put(key, value);
}
}
}
Loading
Loading