diff --git a/docs/supported-libraries.md b/docs/supported-libraries.md index 6cddd284af7d..54a25f62542c 100644 --- a/docs/supported-libraries.md +++ b/docs/supported-libraries.md @@ -90,6 +90,7 @@ These are the supported libraries and frameworks: | [Jedis](https://github.com/xetorthio/jedis) | 1.4+ | N/A | [Database Client Spans] | | [JMS](https://javaee.github.io/javaee-spec/javadocs/javax/jms/package-summary.html) | 1.1+ | N/A | [Messaging Spans] | | [Jodd Http](https://http.jodd.org/) | 4.2+ | N/A | [HTTP Client Spans], [HTTP Client Metrics] | +| [JSON-RPC](https://github.com/briandilley/jsonrpc4j) | 1.3.3+ | N/A | [RPC Client Spans], [RPC Client Metrics], [RPC Server Spans], [RPC Server Metrics] | | [JSP](https://javaee.github.io/javaee-spec/javadocs/javax/servlet/jsp/package-summary.html) | 2.3.x only | N/A | Controller Spans [3] | | [Kotlin Coroutines](https://kotlinlang.org/docs/coroutines-overview.html) | 1.0+ | N/A | Context propagation | | [Ktor](https://github.com/ktorio/ktor) | 1.0+ | [opentelemetry-ktor-1.0](../instrumentation/ktor/ktor-1.0/library),
[opentelemetry-ktor-2.0](../instrumentation/ktor/ktor-2.0/library),
[opentelemetry-ktor-3.0](../instrumentation/ktor/ktor-3.0/library) | [HTTP Client Spans], [HTTP Client Metrics], [HTTP Server Spans], [HTTP Server Metrics] | diff --git a/instrumentation/jsonrpc4j-1.3/javaagent/build.gradle.kts b/instrumentation/jsonrpc4j-1.3/javaagent/build.gradle.kts new file mode 100644 index 000000000000..0fa473ae49cd --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/javaagent/build.gradle.kts @@ -0,0 +1,26 @@ +plugins { + id("otel.javaagent-instrumentation") +} + +muzzle { + pass { + group.set("com.github.briandilley.jsonrpc4j") + module.set("jsonrpc4j") + versions.set("[1.3.3,)") + assertInverse.set(true) + } +} + +dependencies { + implementation(project(":instrumentation:jsonrpc4j-1.3:library")) + + library("com.github.briandilley.jsonrpc4j:jsonrpc4j:1.3.3") + + testImplementation(project(":instrumentation:jsonrpc4j-1.3:testing")) +} + +tasks { + test { + jvmArgs("-Dotel.javaagent.experimental.thread-propagation-debugger.enabled=false") + } +} diff --git a/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcClientInstrumentation.java b/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcClientInstrumentation.java new file mode 100644 index 000000000000..5a985825c8db --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcClientInstrumentation.java @@ -0,0 +1,96 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface; +import static io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3.JsonRpcSingletons.CLIENT_INSTRUMENTER; +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_3.SimpleJsonRpcRequest; +import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.SimpleJsonRpcResponse; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.util.Map; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class JsonRpcClientInstrumentation 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.Argument(3) Map extraHeaders, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + Context parentContext = Context.current(); + SimpleJsonRpcRequest request = new SimpleJsonRpcRequest(methodName, argument); + if (!CLIENT_INSTRUMENTER.shouldStart(parentContext, request)) { + return; + } + + context = 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.Argument(3) Map extraHeaders, + @Advice.Return Object result, + @Advice.Thrown Throwable throwable, + @Advice.Local("otelContext") Context context, + @Advice.Local("otelScope") Scope scope) { + if (scope == null) { + return; + } + + scope.close(); + CLIENT_INSTRUMENTER.end( + context, + new SimpleJsonRpcRequest(methodName, argument), + new SimpleJsonRpcResponse(result), + throwable); + } + } +} diff --git a/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcInstrumentationModule.java b/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcInstrumentationModule.java new file mode 100644 index 000000000000..7905681868de --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcInstrumentationModule.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3; + +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.3"); + } + + @Override + public List typeInstrumentations() { + return asList( + new JsonRpcServerInstrumentation(), + new JsonRpcClientInstrumentation(), + new JsonRpcProxyInstrumentation()); + } +} diff --git a/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcProxyInstrumentation.java b/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcProxyInstrumentation.java new file mode 100644 index 000000000000..4c894fe89679 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcProxyInstrumentation.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.isPrivate; +import static net.bytebuddy.matcher.ElementMatchers.isStatic; +import static net.bytebuddy.matcher.ElementMatchers.named; + +import com.googlecode.jsonrpc4j.IJsonRpcClient; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.util.Map; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class JsonRpcProxyInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher classLoaderOptimization() { + return hasClassesNamed("com.googlecode.jsonrpc4j.ProxyUtil"); + } + + @Override + public ElementMatcher typeMatcher() { + return named("com.googlecode.jsonrpc4j.ProxyUtil"); + } + + @Override + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + isMethod().and(isStatic()).and(isPrivate()).and(named("createClientProxy")), + this.getClass().getName() + "$CreateClientProxyAdvice"); + } + + @SuppressWarnings({"unused"}) + public static class CreateClientProxyAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.Argument(0) ClassLoader classLoader, + @Advice.Argument(1) Class proxyInterface, + @Advice.Argument(2) IJsonRpcClient client, + @Advice.Argument(3) Map extraHeaders, + @Advice.Return(readOnly = false) Object proxy) { + proxy = + JsonRpcSingletons.instrumentCreateClientProxy( + classLoader, proxyInterface, client, extraHeaders); + } + } +} diff --git a/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcServerInstrumentation.java b/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcServerInstrumentation.java new file mode 100644 index 000000000000..da97523bca72 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcServerInstrumentation.java @@ -0,0 +1,84 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3; + +import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed; +import static io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3.JsonRpcSingletons.SERVER_INVOCATION_LISTENER; +import static net.bytebuddy.matcher.ElementMatchers.isConstructor; +import static net.bytebuddy.matcher.ElementMatchers.isMethod; +import static net.bytebuddy.matcher.ElementMatchers.named; + +import com.googlecode.jsonrpc4j.InvocationListener; +import com.googlecode.jsonrpc4j.JsonRpcBasicServer; +import com.googlecode.jsonrpc4j.MultipleInvocationListener; +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.implementation.bytecode.assign.Assigner; +import net.bytebuddy.matcher.ElementMatcher; + +public class JsonRpcServerInstrumentation 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"); + + transformer.applyAdviceToMethod( + isMethod().and(named("setInvocationListener")), + this.getClass().getName() + "$SetInvocationListenerAdvice"); + } + + @SuppressWarnings("unused") + public static class ConstructorAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void setInvocationListener( + @Advice.This JsonRpcBasicServer jsonRpcServer, + @Advice.FieldValue(value = "invocationListener", readOnly = false) + InvocationListener invocationListener) { + invocationListener = SERVER_INVOCATION_LISTENER; + } + } + + @SuppressWarnings("unused") + public static class SetInvocationListenerAdvice { + + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void setInvocationListener( + @Advice.This JsonRpcBasicServer jsonRpcServer, + @Advice.Argument(value = 0, readOnly = false, typing = Assigner.Typing.DYNAMIC) + InvocationListener invocationListener) { + VirtualField instrumented = + VirtualField.find(JsonRpcBasicServer.class, Boolean.class); + if (!Boolean.TRUE.equals(instrumented.get(jsonRpcServer))) { + if (invocationListener == null) { + invocationListener = SERVER_INVOCATION_LISTENER; + } else if (invocationListener instanceof MultipleInvocationListener) { + ((MultipleInvocationListener) invocationListener) + .addInvocationListener(SERVER_INVOCATION_LISTENER); + } else { + invocationListener = + new MultipleInvocationListener(invocationListener, SERVER_INVOCATION_LISTENER); + } + + instrumented.set(jsonRpcServer, true); + } + } + } +} diff --git a/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcSingletons.java b/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcSingletons.java new file mode 100644 index 000000000000..ed3b6115d1cc --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/JsonRpcSingletons.java @@ -0,0 +1,85 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3; + +import com.googlecode.jsonrpc4j.IJsonRpcClient; +import com.googlecode.jsonrpc4j.InvocationListener; +import com.googlecode.jsonrpc4j.ReflectionUtil; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter; +import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.JsonRpcTelemetry; +import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.SimpleJsonRpcRequest; +import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.SimpleJsonRpcResponse; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Map; + +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() {} + + @SuppressWarnings({"unchecked"}) + public static T instrumentCreateClientProxy( + ClassLoader classLoader, + Class proxyInterface, + IJsonRpcClient client, + Map extraHeaders) { + return (T) + Proxy.newProxyInstance( + classLoader, + new Class[] {proxyInterface}, + new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + + Object arguments = ReflectionUtil.parseArguments(method, args); + String methodName = method.getName(); // todo + + // before invoke + Context parentContext = Context.current(); + SimpleJsonRpcRequest request = new SimpleJsonRpcRequest(method, args); + if (!CLIENT_INSTRUMENTER.shouldStart(parentContext, request)) { + return client.invoke( + methodName, arguments, method.getGenericReturnType(), extraHeaders); + } + + Context context = CLIENT_INSTRUMENTER.start(parentContext, request); + Scope scope = context.makeCurrent(); + try { + Object result = + client.invoke( + methodName, arguments, method.getGenericReturnType(), extraHeaders); + scope.close(); + CLIENT_INSTRUMENTER.end( + context, + new SimpleJsonRpcRequest(method, args), + new SimpleJsonRpcResponse(result), + null); + return result; + } catch (Throwable t) { + // after invoke + scope.close(); + CLIENT_INSTRUMENTER.end(context, request, null, t); + throw t; + } + } + }); + } +} diff --git a/instrumentation/jsonrpc4j-1.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/AgentJsonRpcTest.java b/instrumentation/jsonrpc4j-1.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/AgentJsonRpcTest.java new file mode 100644 index 000000000000..57ba6e371908 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/jsonrpc4j/v1_3/AgentJsonRpcTest.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.jsonrpc4j.v1_3; + +import com.googlecode.jsonrpc4j.JsonRpcBasicServer; +import io.opentelemetry.instrumentation.jsonrpc4j.v1_3.AbstractJsonRpcTest; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +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.3/library/README.md b/instrumentation/jsonrpc4j-1.3/library/README.md new file mode 100644 index 000000000000..17174b9bf00c --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/library/README.md @@ -0,0 +1,39 @@ +# Library Instrumentation for jsonrpc4j 1.3.3+ + +Provides OpenTelemetry instrumentation for [jsonrpc4j](https://github.com/briandilley/jsonrpc4j) server. + +## Quickstart + +### Add the following dependencies to your project + +Replace `OPENTELEMETRY_VERSION` with the [latest release](https://search.maven.org/search?q=g:io.opentelemetry.instrumentation%20AND%20a:opentelemetry-jsonrpc4j-1.3). + +For Maven, add the following to your `pom.xml` dependencies: + +```xml + + + io.opentelemetry.instrumentation + opentelemetry-jsonrpc4j-1.3 + OPENTELEMETRY_VERSION + + +``` + +For Gradle, add the following to your dependencies: + +```groovy +implementation("io.opentelemetry.instrumentation:opentelemetry-jsonrpc4j-1.3:OPENTELEMETRY_VERSION") +``` + +### Usage + +The instrumentation library provides the implementation of `InvocationListener` to provide OpenTelemetry-based spans and context propagation. + +```java +// For server-side, attatch the invocation listener to your service. +JsonRpcBasicServer configureServer(OpenTelemetry openTelemetry, JsonRpcBasicServer server) { + JsonRpcTelemetry jsonrpcTelemetry = JsonRpcTelemetry.create(openTelemetry); + return server.setInvocationListener(jsonrpcTelemetry.newServerInvocationListener()); +} +``` diff --git a/instrumentation/jsonrpc4j-1.3/library/build.gradle.kts b/instrumentation/jsonrpc4j-1.3/library/build.gradle.kts new file mode 100644 index 000000000000..345f9d27f44f --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/library/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("otel.library-instrumentation") +} + +val jacksonVersion = "2.13.3" + +dependencies { + library("com.github.briandilley.jsonrpc4j:jsonrpc4j:1.3.3") + + library("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion") + + testImplementation(project(":instrumentation:jsonrpc4j-1.3:testing")) +} diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcClientAttributesExtractor.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcClientAttributesExtractor.java new file mode 100644 index 000000000000..5696bf2ee898 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcClientAttributesExtractor.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jsonrpc4j.v1_3; + +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 { + + @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.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcClientAttributesGetter.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcClientAttributesGetter.java new file mode 100644 index 000000000000..c85de10f4d5c --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcClientAttributesGetter.java @@ -0,0 +1,33 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jsonrpc4j.v1_3; + +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) { + if (request.getMethod() != null) { + return request.getMethod().getDeclaringClass().getName(); + } + return "NOT_AVAILABLE"; + } + + @Override + public String getMethod(SimpleJsonRpcRequest request) { + return request.getMethodName(); + } +} diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcClientSpanNameExtractor.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcClientSpanNameExtractor.java new file mode 100644 index 000000000000..a4ab989eef58 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcClientSpanNameExtractor.java @@ -0,0 +1,20 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jsonrpc4j.v1_3; + +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; +import java.lang.reflect.Method; + +public class JsonRpcClientSpanNameExtractor implements SpanNameExtractor { + @Override + public String extract(SimpleJsonRpcRequest request) { + if (request.getMethod() == null) { + return request.getMethodName(); + } + Method method = request.getMethod(); + return String.format("%s/%s", method.getDeclaringClass().getName(), method.getName()); + } +} diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcRequest.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcRequest.java new file mode 100644 index 000000000000..bfa0f49b0168 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcRequest.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jsonrpc4j.v1_3; + +import com.fasterxml.jackson.databind.JsonNode; +import java.lang.reflect.Method; +import java.util.List; + +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.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcRequestGetter.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcRequestGetter.java new file mode 100644 index 000000000000..8f3fd51f9b27 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcRequestGetter.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jsonrpc4j.v1_3; + +import io.opentelemetry.context.propagation.TextMapGetter; +import java.util.ArrayList; +import javax.annotation.Nullable; + +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.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcResponse.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcResponse.java new file mode 100644 index 000000000000..9279e492d324 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcResponse.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jsonrpc4j.v1_3; + +import com.fasterxml.jackson.databind.JsonNode; +import java.lang.reflect.Method; +import java.util.List; + +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.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerAttributesExtractor.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerAttributesExtractor.java new file mode 100644 index 000000000000..36c46eaa2e1c --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerAttributesExtractor.java @@ -0,0 +1,55 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jsonrpc4j.v1_3; + +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"); + + @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.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerAttributesGetter.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerAttributesGetter.java new file mode 100644 index 000000000000..bd69490a944a --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerAttributesGetter.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jsonrpc4j.v1_3; + +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().getName(); + } + + @Override + public String getMethod(JsonRpcRequest request) { + return request.getMethod().getName(); + } +} diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerSpanNameExtractor.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerSpanNameExtractor.java new file mode 100644 index 000000000000..0639a40cc891 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerSpanNameExtractor.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jsonrpc4j.v1_3; + +import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor; +import java.lang.reflect.Method; + +public class JsonRpcServerSpanNameExtractor implements SpanNameExtractor { + // Follow https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/#span-name + @Override + public String extract(JsonRpcRequest request) { + Method method = request.getMethod(); + return String.format("%s/%s", method.getDeclaringClass().getName(), method.getName()); + } +} diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerSpanStatusExtractor.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerSpanStatusExtractor.java new file mode 100644 index 000000000000..27509950f1e7 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcServerSpanStatusExtractor.java @@ -0,0 +1,37 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jsonrpc4j.v1_3; + +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.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcTelemetry.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcTelemetry.java new file mode 100644 index 000000000000..0c54be195e7d --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcTelemetry.java @@ -0,0 +1,46 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jsonrpc4j.v1_3; + +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; + private final ContextPropagators propagators; + + JsonRpcTelemetry( + Instrumenter serverInstrumenter, + Instrumenter clientInstrumenter, + ContextPropagators propagators) { + this.serverInstrumenter = serverInstrumenter; + this.clientInstrumenter = clientInstrumenter; + this.propagators = propagators; + } + + public InvocationListener newServerInvocationListener() { + return new OpenTelemetryJsonRpcInvocationListener(serverInstrumenter); + } + + public Instrumenter getClientInstrumenter() { + return clientInstrumenter; + } + + public ContextPropagators getPropagators() { + return propagators; + } +} diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcTelemetryBuilder.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcTelemetryBuilder.java new file mode 100644 index 000000000000..74fdaf810988 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/JsonRpcTelemetryBuilder.java @@ -0,0 +1,98 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jsonrpc4j.v1_3; + +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.3"; + + private final OpenTelemetry openTelemetry; + + private final List< + AttributesExtractor> + 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.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/OpenTelemetryJsonRpcInvocationListener.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/OpenTelemetryJsonRpcInvocationListener.java new file mode 100644 index 000000000000..a7337f224140 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/OpenTelemetryJsonRpcInvocationListener.java @@ -0,0 +1,69 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jsonrpc4j.v1_3; + +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) { + 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) { + 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.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/SimpleJsonRpcRequest.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/SimpleJsonRpcRequest.java new file mode 100644 index 000000000000..4d1a09d678a6 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/SimpleJsonRpcRequest.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jsonrpc4j.v1_3; + +import java.lang.reflect.Method; + +public final class SimpleJsonRpcRequest { + + private final String methodName; + private final Object argument; + private Method method; + + public SimpleJsonRpcRequest(String methodName, Object argument) { + this.methodName = methodName; + this.argument = argument; + } + + public SimpleJsonRpcRequest(Method method, Object argument) { + this.method = method; + this.methodName = method.getName(); + this.argument = argument; + } + + public String getMethodName() { + return methodName; + } + + public Object getArgument() { + return argument; + } + + public Method getMethod() { + return method; + } +} diff --git a/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/SimpleJsonRpcResponse.java b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/SimpleJsonRpcResponse.java new file mode 100644 index 000000000000..e1ea2ac8666e --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/library/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/SimpleJsonRpcResponse.java @@ -0,0 +1,19 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jsonrpc4j.v1_3; + +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.3/library/src/test/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/LibraryJsonRpcTest.java b/instrumentation/jsonrpc4j-1.3/library/src/test/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/LibraryJsonRpcTest.java new file mode 100644 index 000000000000..63e017601813 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/library/src/test/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/LibraryJsonRpcTest.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jsonrpc4j.v1_3; + +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.3/testing/build.gradle.kts b/instrumentation/jsonrpc4j-1.3/testing/build.gradle.kts new file mode 100644 index 000000000000..35775d794a20 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/testing/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("otel.java-conventions") +} + +val jsonrpcVersion = "1.3.3" + +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.3/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/AbstractJsonRpcTest.java b/instrumentation/jsonrpc4j-1.3/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/AbstractJsonRpcTest.java new file mode 100644 index 000000000000..51c79dbd9071 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/AbstractJsonRpcTest.java @@ -0,0 +1,101 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jsonrpc4j.v1_3; + +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( + "io.opentelemetry.instrumentation.jsonrpc4j.v1_3.CalculatorService/add") + .hasKind(SpanKind.SERVER) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(RPC_SYSTEM, "jsonrpc"), + equalTo(RPC_JSONRPC_VERSION, "2.0"), + equalTo( + RPC_SERVICE, + "io.opentelemetry.instrumentation.jsonrpc4j.v1_3.CalculatorService"), + equalTo(RPC_METHOD, "add"), + equalTo(RPC_JSONRPC_ERROR_CODE, 0L)))); + testing() + .waitAndAssertMetrics( + "io.opentelemetry.jsonrpc4j-1.3", + "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, + "io.opentelemetry.instrumentation.jsonrpc4j.v1_3.CalculatorService"), + equalTo(RPC_SYSTEM, "jsonrpc")))))); + } +} diff --git a/instrumentation/jsonrpc4j-1.3/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/CalculatorService.java b/instrumentation/jsonrpc4j-1.3/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/CalculatorService.java new file mode 100644 index 000000000000..8821b7d2b858 --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/CalculatorService.java @@ -0,0 +1,15 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jsonrpc4j.v1_3; + +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.3/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/CalculatorServiceImpl.java b/instrumentation/jsonrpc4j-1.3/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/CalculatorServiceImpl.java new file mode 100644 index 000000000000..c01b9c99996c --- /dev/null +++ b/instrumentation/jsonrpc4j-1.3/testing/src/main/java/io/opentelemetry/instrumentation/jsonrpc4j/v1_3/CalculatorServiceImpl.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jsonrpc4j.v1_3; + +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..5a7294e49e90 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.3:javaagent") +include(":instrumentation:jsonrpc4j-1.3:library") +include(":instrumentation:jsonrpc4j-1.3: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")