From 0190c13027a15d9f67ba73761c359d0156a58439 Mon Sep 17 00:00:00 2001 From: chenlujjj <953546398@qq.com> Date: Tue, 7 Jan 2025 18:44:33 +0800 Subject: [PATCH] feat: support instrumentation for jsonrpc4j --- .../jsonrpc4j-1.6/javaagent/build.gradle.kts | 29 ++++++ .../JsonRpcClientBuilderInstrumentation.java | 87 +++++++++++++++++ .../v1_6/JsonRpcInstrumentationModule.java | 24 +++++ .../JsonRpcServerBuilderInstrumentation.java | 52 ++++++++++ .../jsonrpc4j/v1_6/JsonRpcSingletons.java | 29 ++++++ ...ServiceExporterBuilderInstrumentation.java | 50 ++++++++++ .../jsonrpc4j/v1_6/AgentJsonRpcTest.java | 29 ++++++ .../jsonrpc4j-1.6/library/build.gradle.kts | 14 +++ .../JsonRpcClientAttributesExtractor.java | 33 +++++++ .../v1_6/JsonRpcClientAttributesGetter.java | 25 +++++ .../v1_6/JsonRpcClientSpanNameExtractor.java | 10 ++ .../jsonrpc4j/v1_6/JsonRpcRequest.java | 27 ++++++ .../jsonrpc4j/v1_6/JsonRpcRequestGetter.java | 20 ++++ .../jsonrpc4j/v1_6/JsonRpcResponse.java | 29 ++++++ .../JsonRpcServerAttributesExtractor.java | 52 ++++++++++ .../v1_6/JsonRpcServerAttributesGetter.java | 25 +++++ .../v1_6/JsonRpcServerSpanNameExtractor.java | 10 ++ .../JsonRpcServerSpanStatusExtractor.java | 31 ++++++ .../jsonrpc4j/v1_6/JsonRpcTelemetry.java | 37 ++++++++ .../v1_6/JsonRpcTelemetryBuilder.java | 94 +++++++++++++++++++ ...penTelemetryJsonRpcInvocationListener.java | 65 +++++++++++++ .../jsonrpc4j/v1_6/SimpleJsonRpcRequest.java | 21 +++++ .../jsonrpc4j/v1_6/SimpleJsonRpcResponse.java | 15 +++ .../jsonrpc4j/v1_6/LibraryJsonRpcTest.java | 27 ++++++ .../jsonrpc4j-1.6/testing/build.gradle.kts | 18 ++++ .../jsonrpc4j/v1_6/AbstractJsonRpcTest.java | 90 ++++++++++++++++++ .../jsonrpc4j/v1_6/CalculatorService.java | 10 ++ .../jsonrpc4j/v1_6/CalculatorServiceImpl.java | 13 +++ settings.gradle.kts | 3 + 29 files changed, 969 insertions(+) create mode 100644 instrumentation/jsonrpc4j-1.6/javaagent/build.gradle.kts create mode 100644 instrumentation/jsonrpc4j-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/JsonRpcClientBuilderInstrumentation.java create mode 100644 instrumentation/jsonrpc4j-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/JsonRpcInstrumentationModule.java create mode 100644 instrumentation/jsonrpc4j-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/JsonRpcServerBuilderInstrumentation.java create mode 100644 instrumentation/jsonrpc4j-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/JsonRpcSingletons.java create mode 100644 instrumentation/jsonrpc4j-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/JsonServiceExporterBuilderInstrumentation.java create mode 100644 instrumentation/jsonrpc4j-1.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/AgentJsonRpcTest.java create mode 100644 instrumentation/jsonrpc4j-1.6/library/build.gradle.kts create mode 100644 instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcClientAttributesExtractor.java create mode 100644 instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcClientAttributesGetter.java create mode 100644 instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcClientSpanNameExtractor.java create mode 100644 instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcRequest.java create mode 100644 instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcRequestGetter.java create mode 100644 instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcResponse.java create mode 100644 instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcServerAttributesExtractor.java create mode 100644 instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcServerAttributesGetter.java create mode 100644 instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcServerSpanNameExtractor.java create mode 100644 instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcServerSpanStatusExtractor.java create mode 100644 instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcTelemetry.java create mode 100644 instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcTelemetryBuilder.java create mode 100644 instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/OpenTelemetryJsonRpcInvocationListener.java create mode 100644 instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/SimpleJsonRpcRequest.java create mode 100644 instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/SimpleJsonRpcResponse.java create mode 100644 instrumentation/jsonrpc4j-1.6/library/src/test/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/LibraryJsonRpcTest.java create mode 100644 instrumentation/jsonrpc4j-1.6/testing/build.gradle.kts create mode 100644 instrumentation/jsonrpc4j-1.6/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/AbstractJsonRpcTest.java create mode 100644 instrumentation/jsonrpc4j-1.6/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/CalculatorService.java create mode 100644 instrumentation/jsonrpc4j-1.6/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/CalculatorServiceImpl.java diff --git a/instrumentation/jsonrpc4j-1.6/javaagent/build.gradle.kts b/instrumentation/jsonrpc4j-1.6/javaagent/build.gradle.kts new file mode 100644 index 000000000000..77b5ef767523 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/javaagent/build.gradle.kts @@ -0,0 +1,29 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +muzzle { + pass { + group.set("com.github.briandilley.jsonrpc4j") + module.set("jsonrpc4j") + versions.set("[1.6,)") + assertInverse.set(true) + } +} + +val jsonrpcVersion = "1.6" + +dependencies { + implementation(project(":instrumentation:jsonrpc4j-1.6:library")) + implementation("com.github.briandilley.jsonrpc4j:jsonrpc4j:$jsonrpcVersion") + testImplementation(project(":instrumentation:jsonrpc4j-1.6:testing")) +} + + +tasks { + test { + systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) + jvmArgs("-Dotel.javaagent.experimental.thread-propagation-debugger.enabled=false") + jvmArgs("-Dotel.instrumentation.common.experimental.controller-telemetry.enabled=true") + } +} diff --git a/instrumentation/jsonrpc4j-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/JsonRpcClientBuilderInstrumentation.java b/instrumentation/jsonrpc4j-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/JsonRpcClientBuilderInstrumentation.java new file mode 100644 index 000000000000..0aa92b064b85 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/JsonRpcClientBuilderInstrumentation.java @@ -0,0 +1,87 @@ +package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_6; + +import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; + +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.jsonrpc4j.v1_6.SimpleJsonRpcRequest; +import io.opentelemetry.instrumentation.jsonrpc4j.v1_6.SimpleJsonRpcResponse; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class JsonRpcClientBuilderInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed("com.googlecode.jsonrpc4j.IJsonRpcClient"); + } + + @Override + public ElementMatcher typeMatcher() { + // match JsonRpcHttpClient and JsonRpcRestClient + return implementsInterface(named("com.googlecode.jsonrpc4j.IJsonRpcClient")); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod() + .and(named("invoke")) + .and(takesArguments(4)) + .and(takesArgument(0, String.class)) + .and(takesArgument(1, Object.class)) + .and(takesArgument(2, named("java.lang.reflect.Type"))) + .and(takesArgument(3, named("java.util.Map"))) + .and(returns(Object.class)), + this.getClass().getName() + "$InvokeAdvice"); + } + + @SuppressWarnings("unused") + public static class InvokeAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.Argument(0) String methodName, + @Advice.Argument(1) Object argument, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + Context parentContext = currentContext(); + SimpleJsonRpcRequest request = new SimpleJsonRpcRequest( + methodName, + argument + ); + if (!JsonRpcSingletons.CLIENT_INSTRUMENTER.shouldStart(parentContext, request)) { + return; + } + + context = JsonRpcSingletons.CLIENT_INSTRUMENTER.start(parentContext, request); + scope = context.makeCurrent(); + } + + @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class) + public static void onExit( + @Advice.Argument(0) String methodName, + @Advice.Argument(1) Object argument, + @Advice.Return Object result, + @Advice.Thrown Throwable throwable, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + if (scope == null) { + return; + } + + scope.close(); + JsonRpcSingletons.CLIENT_INSTRUMENTER.end(context, new SimpleJsonRpcRequest(methodName, argument), new SimpleJsonRpcResponse(result), throwable); + } + } +} diff --git a/instrumentation/jsonrpc4j-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/JsonRpcInstrumentationModule.java b/instrumentation/jsonrpc4j-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/JsonRpcInstrumentationModule.java new file mode 100644 index 000000000000..a663dc8e0a15 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/JsonRpcInstrumentationModule.java @@ -0,0 +1,24 @@ +package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_6; + +import static java.util.Arrays.asList; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import java.util.List; + +@AutoService(InstrumentationModule.class) +public class JsonRpcInstrumentationModule extends InstrumentationModule { + public JsonRpcInstrumentationModule() { + super("jsonrpc4j", "jsonrpc4j-1.6"); + } + + @Override + public List typeInstrumentations() { + return asList( + new JsonRpcServerBuilderInstrumentation(), + new JsonServiceExporterBuilderInstrumentation(), + new JsonRpcClientBuilderInstrumentation() + ); + } +} diff --git a/instrumentation/jsonrpc4j-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/JsonRpcServerBuilderInstrumentation.java b/instrumentation/jsonrpc4j-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/JsonRpcServerBuilderInstrumentation.java new file mode 100644 index 000000000000..677b951d8c5d --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/JsonRpcServerBuilderInstrumentation.java @@ -0,0 +1,52 @@ +package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_6; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.named; + +import com.googlecode.jsonrpc4j.InvocationListener; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.description.type.TypeDescription; +import io.opentelemetry.instrumentation.api.util.VirtualField; +import net.bytebuddy.matcher.ElementMatcher; +import com.googlecode.jsonrpc4j.JsonRpcBasicServer; +import net.bytebuddy.asm.Advice; + +public class JsonRpcServerBuilderInstrumentation implements TypeInstrumentation { + + + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed("com.googlecode.jsonrpc4j.JsonRpcBasicServer"); + } + + @Override + public ElementMatcher typeMatcher() { + return named("com.googlecode.jsonrpc4j.JsonRpcBasicServer"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isConstructor(), + this.getClass().getName() + "$ConstructorAdvice"); + } + + @SuppressWarnings("unused") + public static class ConstructorAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void setInvocationListener( + @Advice.This JsonRpcBasicServer jsonRpcServer, + @Advice.FieldValue("invocationListener") InvocationListener invocationListener) { + VirtualField instrumented = + VirtualField.find(JsonRpcBasicServer.class, Boolean.class); + if (!Boolean.TRUE.equals(instrumented.get(jsonRpcServer))) { + jsonRpcServer.setInvocationListener(JsonRpcSingletons.SERVER_INVOCATION_LISTENER); + instrumented.set(jsonRpcServer, true); + } + } + } + +} diff --git a/instrumentation/jsonrpc4j-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/JsonRpcSingletons.java b/instrumentation/jsonrpc4j-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/JsonRpcSingletons.java new file mode 100644 index 000000000000..e63109db170f --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/JsonRpcSingletons.java @@ -0,0 +1,29 @@ +package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_6; + +import com.googlecode.jsonrpc4j.InvocationListener; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.jsonrpc4j.v1_6.JsonRpcTelemetry; +import io.opentelemetry.instrumentation.jsonrpc4j.v1_6.SimpleJsonRpcRequest; +import io.opentelemetry.instrumentation.jsonrpc4j.v1_6.SimpleJsonRpcResponse; + + +public final class JsonRpcSingletons { + + public static final InvocationListener SERVER_INVOCATION_LISTENER; + + public static final Instrumenter CLIENT_INSTRUMENTER; + + + static { + JsonRpcTelemetry telemetry = + JsonRpcTelemetry.builder(GlobalOpenTelemetry.get()) + .build(); + + SERVER_INVOCATION_LISTENER = telemetry.newServerInvocationListener(); + CLIENT_INSTRUMENTER = telemetry.getClientInstrumenter(); + } + + + private JsonRpcSingletons() {} +} diff --git a/instrumentation/jsonrpc4j-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/JsonServiceExporterBuilderInstrumentation.java b/instrumentation/jsonrpc4j-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/JsonServiceExporterBuilderInstrumentation.java new file mode 100644 index 000000000000..a69587bccc24 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/JsonServiceExporterBuilderInstrumentation.java @@ -0,0 +1,50 @@ +package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_6; + +import com.googlecode.jsonrpc4j.JsonRpcServer; +import com.googlecode.jsonrpc4j.spring.JsonServiceExporter; +import io.opentelemetry.instrumentation.api.util.VirtualField; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; + +public class JsonServiceExporterBuilderInstrumentation implements TypeInstrumentation { + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed("com.googlecode.jsonrpc4j.spring.JsonServiceExporter"); + } + + @Override + public ElementMatcher typeMatcher() { + return named("com.googlecode.jsonrpc4j.spring.JsonServiceExporter"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod().and(named("exportService")), + this.getClass().getName() + "$ExportAdvice"); + } + + @SuppressWarnings("unused") + public static class ExportAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void setInvocationListener( + @Advice.This JsonServiceExporter exporter, + @Advice.FieldValue("jsonRpcServer") JsonRpcServer jsonRpcServer) { + VirtualField instrumented = + VirtualField.find(JsonRpcServer.class, Boolean.class); + if (!Boolean.TRUE.equals(instrumented.get(jsonRpcServer))) { + jsonRpcServer.setInvocationListener(JsonRpcSingletons.SERVER_INVOCATION_LISTENER); + instrumented.set(jsonRpcServer, true); + } + } + } +} diff --git a/instrumentation/jsonrpc4j-1.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/AgentJsonRpcTest.java b/instrumentation/jsonrpc4j-1.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/AgentJsonRpcTest.java new file mode 100644 index 000000000000..0012d13dff88 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_6/AgentJsonRpcTest.java @@ -0,0 +1,29 @@ +package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_6; + +import com.googlecode.jsonrpc4j.JsonRpcBasicServer; +import io.opentelemetry.instrumentation.jsonrpc4j.v1_6.AbstractJsonRpcTest; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class AgentJsonRpcTest extends AbstractJsonRpcTest { + + + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + + @Override + protected InstrumentationExtension testing() { + return testing; + } + + @Override + protected JsonRpcBasicServer configureServer(JsonRpcBasicServer server) { + return server; + } + + + + +} diff --git a/instrumentation/jsonrpc4j-1.6/library/build.gradle.kts b/instrumentation/jsonrpc4j-1.6/library/build.gradle.kts new file mode 100644 index 000000000000..36e5872e74e4 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/library/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + id("otel.library-instrumentation") +} + +val jsonrpcVersion = "1.6" +val jacksonVersion = "2.13.3" + +dependencies { + implementation("com.github.briandilley.jsonrpc4j:jsonrpc4j:$jsonrpcVersion") + + implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion") + + testImplementation(project(":instrumentation:jsonrpc4j-1.6:testing")) +} diff --git a/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcClientAttributesExtractor.java b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcClientAttributesExtractor.java new file mode 100644 index 000000000000..495935bc0456 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcClientAttributesExtractor.java @@ -0,0 +1,33 @@ +package io.opentelemetry.instrumentation.jsonrpc4j.v1_6; + +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.context.Context; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import javax.annotation.Nullable; + +// Check https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/ +final class JsonRpcClientAttributesExtractor implements AttributesExtractor { + +// private final JsonRpcClientAttributesGetter getter; +// +// +// JsonRpcClientAttributesExtractor(JsonRpcClientAttributesGetter getter) { +// this.getter = getter; +// } + + @Override + public void onStart(AttributesBuilder attributes, Context parentContext, + SimpleJsonRpcRequest jsonRpcRequest) { + attributes.put("rpc.jsonrpc.version", "2.0"); + } + + @Override + public void onEnd( + AttributesBuilder attributes, + Context context, + SimpleJsonRpcRequest jsonRpcRequest, + @Nullable SimpleJsonRpcResponse jsonRpcResponse, + @Nullable Throwable error) { + + } +} diff --git a/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcClientAttributesGetter.java b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcClientAttributesGetter.java new file mode 100644 index 000000000000..1d3f6eb654ba --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcClientAttributesGetter.java @@ -0,0 +1,25 @@ +package io.opentelemetry.instrumentation.jsonrpc4j.v1_6; + +import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcAttributesGetter; + +// Check https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md#attributes +// Check https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/ +public enum JsonRpcClientAttributesGetter implements RpcAttributesGetter { + INSTANCE; + + @Override + public String getSystem(SimpleJsonRpcRequest request) { + return "jsonrpc"; + } + + @Override + public String getService(SimpleJsonRpcRequest request) { + // TODO + return "NOT_IMPLEMENTED"; + } + + @Override + public String getMethod(SimpleJsonRpcRequest request) { + return request.getMethodName(); + } +} diff --git a/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcClientSpanNameExtractor.java b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcClientSpanNameExtractor.java new file mode 100644 index 000000000000..9ae18cd74687 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcClientSpanNameExtractor.java @@ -0,0 +1,10 @@ +package io.opentelemetry.instrumentation.jsonrpc4j.v1_6; + +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; + +public class JsonRpcClientSpanNameExtractor implements SpanNameExtractor { + @Override + public String extract(SimpleJsonRpcRequest request) { + return request.getMethodName(); + } +} diff --git a/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcRequest.java b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcRequest.java new file mode 100644 index 000000000000..4cbebce1ea4b --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcRequest.java @@ -0,0 +1,27 @@ +package io.opentelemetry.instrumentation.jsonrpc4j.v1_6; + +import java.lang.reflect.Method; +import java.util.List; +import com.fasterxml.jackson.databind.JsonNode; + +public final class JsonRpcRequest { + + private final Method method; + private final List arguments; + + JsonRpcRequest(Method method, List arguments) { + this.method = method; + this.arguments = arguments; + } + + + public Method getMethod() { + return method; + } + + public List getArguments() { + return arguments; + } + + +} diff --git a/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcRequestGetter.java b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcRequestGetter.java new file mode 100644 index 000000000000..579b616e475c --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcRequestGetter.java @@ -0,0 +1,20 @@ +package io.opentelemetry.instrumentation.jsonrpc4j.v1_6; + +import io.opentelemetry.context.propagation.TextMapGetter; +import javax.annotation.Nullable; +import java.util.ArrayList; + +enum JsonRpcRequestGetter implements TextMapGetter { + INSTANCE; + + @Override + public Iterable keys(JsonRpcRequest request) { + return new ArrayList<>(); + } + + @Override + @Nullable + public String get(@Nullable JsonRpcRequest request, String key) { + return null; + } +} diff --git a/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcResponse.java b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcResponse.java new file mode 100644 index 000000000000..ed9e244cb366 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcResponse.java @@ -0,0 +1,29 @@ +package io.opentelemetry.instrumentation.jsonrpc4j.v1_6; + +import java.lang.reflect.Method; +import java.util.List; +import com.fasterxml.jackson.databind.JsonNode; + +public final class JsonRpcResponse { + private final Method method; + private final List params; + private final Object result; + + JsonRpcResponse(Method method, List params, Object result) { + this.method = method; + this.params = params; + this.result = result; + } + + public Method getMethod() { + return method; + } + + public List getParams() { + return params; + } + + public Object getResult() { + return result; + } +} diff --git a/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcServerAttributesExtractor.java b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcServerAttributesExtractor.java new file mode 100644 index 000000000000..5e51dd2a77dd --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcServerAttributesExtractor.java @@ -0,0 +1,52 @@ +package io.opentelemetry.instrumentation.jsonrpc4j.v1_6; + +import com.googlecode.jsonrpc4j.AnnotationsErrorResolver; +import com.googlecode.jsonrpc4j.DefaultErrorResolver; +import com.googlecode.jsonrpc4j.ErrorResolver; +import com.googlecode.jsonrpc4j.MultipleErrorResolver; +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; + +// Check https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/ +final class JsonRpcServerAttributesExtractor implements AttributesExtractor { + + private static final AttributeKey RPC_JSONRPC_ERROR_CODE = + AttributeKey.longKey("rpc.jsonrpc.error_code"); + + private static final AttributeKey RPC_JSONRPC_ERROR_MESSAGE = + AttributeKey.stringKey("rpc.jsonrpc.error_message"); + +// private final JsonRpcServerAttributesGetter getter; +// +// +// JsonRpcServerAttributesExtractor(JsonRpcServerAttributesGetter getter) { +// this.getter = getter; +// } + + @Override + public void onStart(AttributesBuilder attributes, Context parentContext, + JsonRpcRequest jsonRpcRequest) { + attributes.put("rpc.jsonrpc.version", "2.0"); + } + + @Override + public void onEnd( + AttributesBuilder attributes, + Context context, + JsonRpcRequest jsonRpcRequest, + @Nullable JsonRpcResponse jsonRpcResponse, + @Nullable Throwable error) { + // use the DEFAULT_ERROR_RESOLVER to extract error code and message + if (error != null) { + ErrorResolver errorResolver = new MultipleErrorResolver(AnnotationsErrorResolver.INSTANCE, DefaultErrorResolver.INSTANCE); + ErrorResolver.JsonError jsonError = errorResolver.resolveError(error, jsonRpcRequest.getMethod(), jsonRpcRequest.getArguments()); + attributes.put(RPC_JSONRPC_ERROR_CODE, jsonError.code); + attributes.put(RPC_JSONRPC_ERROR_MESSAGE, jsonError.message); + } else { + attributes.put(RPC_JSONRPC_ERROR_CODE, ErrorResolver.JsonError.OK.code); + } + } +} diff --git a/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcServerAttributesGetter.java b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcServerAttributesGetter.java new file mode 100644 index 000000000000..52b3beebddf2 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcServerAttributesGetter.java @@ -0,0 +1,25 @@ +package io.opentelemetry.instrumentation.jsonrpc4j.v1_6; + +import com.googlecode.jsonrpc4j.JsonRpcService; +import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcAttributesGetter; + +// Check https://github.com/open-telemetry/semantic-conventions/blob/main/docs/rpc/rpc-metrics.md#attributes +// Check https://opentelemetry.io/docs/specs/semconv/rpc/json-rpc/ +public enum JsonRpcServerAttributesGetter implements RpcAttributesGetter { + INSTANCE; + + @Override + public String getSystem(JsonRpcRequest request) { + return "jsonrpc"; + } + + @Override + public String getService(JsonRpcRequest request) { + return request.getMethod().getDeclaringClass().getAnnotation(JsonRpcService.class).value(); + } + + @Override + public String getMethod(JsonRpcRequest request) { + return request.getMethod().getName(); + } +} diff --git a/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcServerSpanNameExtractor.java b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcServerSpanNameExtractor.java new file mode 100644 index 000000000000..244172bbd7d5 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcServerSpanNameExtractor.java @@ -0,0 +1,10 @@ +package io.opentelemetry.instrumentation.jsonrpc4j.v1_6; + +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; + +public class JsonRpcServerSpanNameExtractor implements SpanNameExtractor { + @Override + public String extract(JsonRpcRequest request) { + return request.getMethod().getName(); + } +} diff --git a/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcServerSpanStatusExtractor.java b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcServerSpanStatusExtractor.java new file mode 100644 index 000000000000..90220d9c0bcd --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcServerSpanStatusExtractor.java @@ -0,0 +1,31 @@ +package io.opentelemetry.instrumentation.jsonrpc4j.v1_6; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.SpanStatusExtractor; +import javax.annotation.Nullable; + +public enum JsonRpcServerSpanStatusExtractor implements SpanStatusExtractor { + + INSTANCE; + + /** + * Extracts the status from the response and sets it to the {@code spanStatusBuilder}. + */ + @Override + public void extract(SpanStatusBuilder spanStatusBuilder, JsonRpcRequest jsonRpcRequest, + @Nullable JsonRpcResponse jsonRpcResponse, @Nullable Throwable error) { + if (error == null) { + spanStatusBuilder.setStatus(StatusCode.OK); + } + + // treat client invalid input as OK + if (error instanceof JsonParseException || error instanceof JsonMappingException) { + spanStatusBuilder.setStatus(StatusCode.OK); + } + + spanStatusBuilder.setStatus(StatusCode.ERROR); + } +} diff --git a/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcTelemetry.java b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcTelemetry.java new file mode 100644 index 000000000000..d93e175b83e7 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcTelemetry.java @@ -0,0 +1,37 @@ +package io.opentelemetry.instrumentation.jsonrpc4j.v1_6; + + +import com.googlecode.jsonrpc4j.InvocationListener; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; + +public final class JsonRpcTelemetry { + public static JsonRpcTelemetry create(OpenTelemetry openTelemetry) { + return builder(openTelemetry).build(); + } + + public static JsonRpcTelemetryBuilder builder(OpenTelemetry openTelemetry) { + return new JsonRpcTelemetryBuilder(openTelemetry); + } + + private final Instrumenter serverInstrumenter; + private final Instrumenter clientInstrumenter; + + JsonRpcTelemetry( + Instrumenter serverInstrumenter, + Instrumenter clientInstrumenter, + ContextPropagators propagators) { + this.serverInstrumenter = serverInstrumenter; + this.clientInstrumenter = clientInstrumenter; + } + + + public InvocationListener newServerInvocationListener() { + return new OpenTelemetryJsonRpcInvocationListener(serverInstrumenter); + } + + public Instrumenter getClientInstrumenter() { + return clientInstrumenter; + } +} diff --git a/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcTelemetryBuilder.java b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcTelemetryBuilder.java new file mode 100644 index 000000000000..ecd8046357a3 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/JsonRpcTelemetryBuilder.java @@ -0,0 +1,94 @@ +package io.opentelemetry.instrumentation.jsonrpc4j.v1_6; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcClientAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcClientMetrics; +import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcServerAttributesExtractor; +import io.opentelemetry.instrumentation.api.incubator.semconv.rpc.RpcServerMetrics; +import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.api.instrumenter.InstrumenterBuilder; +import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor; +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; +import java.util.ArrayList; +import java.util.List; + +public class JsonRpcTelemetryBuilder { + + private static final String INSTRUMENTATION_NAME = "io.opentelemetry.jsonrpc4j-1.6"; + + private final OpenTelemetry openTelemetry; + + private final List> + additionalClientExtractors = new ArrayList<>(); + private final List> + additionalServerExtractors = new ArrayList<>(); + + JsonRpcTelemetryBuilder(OpenTelemetry openTelemetry) { + this.openTelemetry = openTelemetry; + } + + + /** + * Adds an extra client-only {@link AttributesExtractor} to invoke to set attributes to + * instrumented items. The {@link AttributesExtractor} will be executed after all default + * extractors. + */ + @CanIgnoreReturnValue + public JsonRpcTelemetryBuilder addClientAttributeExtractor( + AttributesExtractor attributesExtractor) { + additionalClientExtractors.add(attributesExtractor); + return this; + } + + /** + * Adds an extra server-only {@link AttributesExtractor} to invoke to set attributes to + * instrumented items. The {@link AttributesExtractor} will be executed after all default + * extractors. + */ + @CanIgnoreReturnValue + public JsonRpcTelemetryBuilder addServerAttributeExtractor( + AttributesExtractor attributesExtractor) { + additionalServerExtractors.add(attributesExtractor); + return this; + } + + + + + public JsonRpcTelemetry build() { + SpanNameExtractor clientSpanNameExtractor = new JsonRpcClientSpanNameExtractor(); + SpanNameExtractor serverSpanNameExtractor = new JsonRpcServerSpanNameExtractor(); + + InstrumenterBuilder clientInstrumenterBuilder = + Instrumenter.builder(openTelemetry, INSTRUMENTATION_NAME, clientSpanNameExtractor); + + InstrumenterBuilder serverInstrumenterBuilder = + Instrumenter.builder(openTelemetry, INSTRUMENTATION_NAME, serverSpanNameExtractor); + + JsonRpcServerAttributesGetter serverRpcAttributesGetter = JsonRpcServerAttributesGetter.INSTANCE; + JsonRpcClientAttributesGetter clientRpcAttributesGetter = JsonRpcClientAttributesGetter.INSTANCE; + + clientInstrumenterBuilder + .addAttributesExtractor(RpcClientAttributesExtractor.create(clientRpcAttributesGetter)) + .addAttributesExtractors(additionalClientExtractors) + .addAttributesExtractor(new JsonRpcClientAttributesExtractor()) + .addOperationMetrics(RpcClientMetrics.get()); + + serverInstrumenterBuilder + .setSpanStatusExtractor(JsonRpcServerSpanStatusExtractor.INSTANCE) + .addAttributesExtractor(RpcServerAttributesExtractor.create(serverRpcAttributesGetter)) + .addAttributesExtractor(new JsonRpcServerAttributesExtractor()) + .addAttributesExtractors(additionalServerExtractors) + .addOperationMetrics(RpcServerMetrics.get()); + + return new JsonRpcTelemetry( + serverInstrumenterBuilder.buildServerInstrumenter(JsonRpcRequestGetter.INSTANCE), + clientInstrumenterBuilder.buildInstrumenter(SpanKindExtractor.alwaysClient()), + openTelemetry.getPropagators() + ); + + } + +} diff --git a/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/OpenTelemetryJsonRpcInvocationListener.java b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/OpenTelemetryJsonRpcInvocationListener.java new file mode 100644 index 000000000000..02246113fe7d --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/OpenTelemetryJsonRpcInvocationListener.java @@ -0,0 +1,65 @@ +package io.opentelemetry.instrumentation.jsonrpc4j.v1_6; + +import com.fasterxml.jackson.databind.JsonNode; +import com.googlecode.jsonrpc4j.InvocationListener; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import java.lang.reflect.Method; +import java.util.List; + +public final class OpenTelemetryJsonRpcInvocationListener implements InvocationListener { + + private final Instrumenter serverInstrumenter; + + private static final ThreadLocal threadLocalContext = new ThreadLocal<>(); + private static final ThreadLocal threadLocalScope = new ThreadLocal<>(); + + public OpenTelemetryJsonRpcInvocationListener( + Instrumenter serverInstrumenter) { + this.serverInstrumenter = serverInstrumenter; + } + + /** + * This method will be invoked prior to a JSON-RPC service being invoked. + * + * @param method is the method that will be invoked. + * @param arguments are the arguments that will be passed to the method when it is invoked. + */ + @Override + public void willInvoke(Method method, List arguments) { +// System.out.printf("inside willInvoke: method is %s, arguments are %s \n", method.getName(), arguments.toString()); + Context parentContext = Context.current(); + JsonRpcRequest request = new JsonRpcRequest(method, arguments); + if (!serverInstrumenter.shouldStart(parentContext, request)) { + return; + } + + Context context = serverInstrumenter.start(parentContext, request); + threadLocalContext.set(context); + threadLocalScope.set(context.makeCurrent()); + } + + /** + * This method will be invoked after a JSON-RPC service has been invoked. + * + * @param method is the method that will was invoked. + * @param arguments are the arguments that were be passed to the method when it is invoked. + * @param result is the result of the method invocation. If an error arose, this value will be + * null. + * @param t is the throwable that was thrown from the invocation, if no error arose, this value + * will be null. + * @param duration is approximately the number of milliseconds that elapsed during which the method was invoked. + */ + @Override + public void didInvoke(Method method, List arguments, Object result, Throwable t, + long duration) { +// System.out.printf("inside didInvoke: method is %s, arguments are %s \n", method.getName(), arguments.toString()); + JsonRpcRequest request = new JsonRpcRequest(method, arguments); + JsonRpcResponse response = new JsonRpcResponse(method, arguments, result); + threadLocalScope.get().close(); + serverInstrumenter.end(threadLocalContext.get(), request, response, t); + threadLocalContext.remove(); + threadLocalScope.remove(); + } +} diff --git a/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/SimpleJsonRpcRequest.java b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/SimpleJsonRpcRequest.java new file mode 100644 index 000000000000..bef6d86f32bb --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/SimpleJsonRpcRequest.java @@ -0,0 +1,21 @@ +package io.opentelemetry.instrumentation.jsonrpc4j.v1_6; + + +public final class SimpleJsonRpcRequest { + + private final String methodName; + private final Object argument; + + public SimpleJsonRpcRequest(String methodName, Object argument) { + this.methodName = methodName; + this.argument = argument; + } + + public String getMethodName() { + return methodName; + } + + public Object getArgument() { + return argument; + } +} diff --git a/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/SimpleJsonRpcResponse.java b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/SimpleJsonRpcResponse.java new file mode 100644 index 000000000000..8992955cb302 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/SimpleJsonRpcResponse.java @@ -0,0 +1,15 @@ +package io.opentelemetry.instrumentation.jsonrpc4j.v1_6; + +public final class SimpleJsonRpcResponse { + + private final Object result; + + + public SimpleJsonRpcResponse(Object result) { + this.result = result; + } + + public Object getResult() { + return result; + } +} diff --git a/instrumentation/jsonrpc4j-1.6/library/src/test/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/LibraryJsonRpcTest.java b/instrumentation/jsonrpc4j-1.6/library/src/test/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/LibraryJsonRpcTest.java new file mode 100644 index 000000000000..39d7c847d290 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/library/src/test/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/LibraryJsonRpcTest.java @@ -0,0 +1,27 @@ +package io.opentelemetry.instrumentation.jsonrpc4j.v1_6; + +import com.googlecode.jsonrpc4j.JsonRpcBasicServer; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class LibraryJsonRpcTest extends AbstractJsonRpcTest { + + @RegisterExtension + static InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Override + protected InstrumentationExtension testing() { + return testing; + } + + @Override + protected JsonRpcBasicServer configureServer(JsonRpcBasicServer server) { + server.setInvocationListener( + JsonRpcTelemetry.builder(testing.getOpenTelemetry()) + .build() + .newServerInvocationListener()); + return server; + } + +} diff --git a/instrumentation/jsonrpc4j-1.6/testing/build.gradle.kts b/instrumentation/jsonrpc4j-1.6/testing/build.gradle.kts new file mode 100644 index 000000000000..93ff1ba0d961 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/testing/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + id("otel.java-conventions") +} + +val jsonrpcVersion = "1.6" + +dependencies { + api(project(":testing-common")) + + implementation("com.github.briandilley.jsonrpc4j:jsonrpc4j:$jsonrpcVersion") + implementation("com.fasterxml.jackson.core:jackson-databind:2.13.3") + + + // ... +} + + + diff --git a/instrumentation/jsonrpc4j-1.6/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/AbstractJsonRpcTest.java b/instrumentation/jsonrpc4j-1.6/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/AbstractJsonRpcTest.java new file mode 100644 index 000000000000..8e36255adab6 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/AbstractJsonRpcTest.java @@ -0,0 +1,90 @@ +package io.opentelemetry.instrumentation.jsonrpc4j.v1_6; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_JSONRPC_ERROR_CODE; +import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_JSONRPC_VERSION; +import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_METHOD; +import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SERVICE; +import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SYSTEM; +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.googlecode.jsonrpc4j.JsonRpcBasicServer; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.testing.internal.jackson.databind.JsonNode; +import io.opentelemetry.testing.internal.jackson.databind.ObjectMapper; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +@SuppressWarnings("deprecation") // using deprecated semconv +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public abstract class AbstractJsonRpcTest { + + protected abstract InstrumentationExtension testing(); + + protected abstract JsonRpcBasicServer configureServer(JsonRpcBasicServer server); + + + @Test + void testServer() throws IOException { + CalculatorService calculator = new CalculatorServiceImpl(); + JsonRpcBasicServer server = configureServer(new JsonRpcBasicServer(calculator, CalculatorService.class)); + + JsonNode response = testing() + .runWithSpan( + "parent", + () -> { + InputStream inputStream = new ByteArrayInputStream("{\"jsonrpc\":\"2.0\",\"method\":\"add\",\"params\":[1,2],\"id\":1}".getBytes(UTF_8)); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + server.handleRequest(inputStream, outputStream); + + // Read the JsonNode from the InputStream + ObjectMapper objectMapper = new ObjectMapper(); + return objectMapper.readTree(new ByteArrayInputStream(outputStream.toByteArray())); + }); + + assertThat(response.get("result").asInt()).isEqualTo(3); + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName("add") + .hasKind(SpanKind.SERVER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(RPC_SYSTEM, "jsonrpc"), + equalTo(RPC_JSONRPC_VERSION, "2.0"), + equalTo(RPC_SERVICE, "/calculator"), + equalTo(RPC_METHOD, "add"), + equalTo(RPC_JSONRPC_ERROR_CODE, 0L)))); + testing() + .waitAndAssertMetrics( + "io.opentelemetry.jsonrpc4j-1.6", + "rpc.server.duration", + metrics -> + metrics.anySatisfy( + metric -> + assertThat(metric) + .hasUnit("ms") + .hasHistogramSatisfying( + histogram -> + histogram.hasPointsSatisfying( + point -> + point.hasAttributesSatisfying( + equalTo(RPC_METHOD, "add"), + equalTo(RPC_SERVICE, "/calculator"), + equalTo(RPC_SYSTEM, "jsonrpc") + ))))); + + + } + +} diff --git a/instrumentation/jsonrpc4j-1.6/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/CalculatorService.java b/instrumentation/jsonrpc4j-1.6/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/CalculatorService.java new file mode 100644 index 000000000000..c86fe23fae47 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/CalculatorService.java @@ -0,0 +1,10 @@ +package io.opentelemetry.instrumentation.jsonrpc4j.v1_6; + +import com.googlecode.jsonrpc4j.JsonRpcService; + +@JsonRpcService("/calculator") +public interface CalculatorService { + int add(int a, int b) throws Throwable; + + int subtract(int a, int b) throws Throwable; +} diff --git a/instrumentation/jsonrpc4j-1.6/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/CalculatorServiceImpl.java b/instrumentation/jsonrpc4j-1.6/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/CalculatorServiceImpl.java new file mode 100644 index 000000000000..c02aeac56784 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.6/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_6/CalculatorServiceImpl.java @@ -0,0 +1,13 @@ +package io.opentelemetry.instrumentation.jsonrpc4j.v1_6; + +public class CalculatorServiceImpl implements CalculatorService { + @Override + public int add(int a, int b) { + return a + b; + } + + @Override + public int subtract(int a, int b) { + return a - b; + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 0e4cda313a8d..624f4df4eacb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -345,6 +345,9 @@ include(":instrumentation:jsf:jsf-mojarra-3.0:javaagent") include(":instrumentation:jsf:jsf-myfaces-1.2:javaagent") include(":instrumentation:jsf:jsf-myfaces-3.0:javaagent") include(":instrumentation:jsp-2.3:javaagent") +include(":instrumentation:jsonrpc4j-1.6:javaagent") +include(":instrumentation:jsonrpc4j-1.6:library") +include(":instrumentation:jsonrpc4j-1.6:testing") include(":instrumentation:kafka:kafka-clients:kafka-clients-0.11:bootstrap") include(":instrumentation:kafka:kafka-clients:kafka-clients-0.11:javaagent") include(":instrumentation:kafka:kafka-clients:kafka-clients-0.11:testing")